2014 Multi-University Training Contest 2

时间:2022-08-14 12:54:29

官方解题报告:http://blog.sina.com.cn/s/blog_a19ad7a10102uyet.html

ZCC Loves Intersection

2014 Multi-University Training Contest 2

2014 Multi-University Training Contest 2

ZCC Loves COT

首先考虑一维下的版本。(数列,区间加,区间和)

显然可以使用线段树,但是线段树推广到高维度的难度较大。

针对本题先修改再询问的特点,我们可以在修改时只保存差分后的数组。

在处理询问前求前缀和还原原数列。之后再求一次前缀和就可以做到O(1)回答询问了。

现在试图把它推广到二维。(三角形,子三角形加,子三角形和)

如果朴素地对每一行应用一维的差分法,最后得到的标记会像下面一样:

2014 Multi-University Training Contest 2

其中+1标记和-1标记都是连续的一段。

标记也是一种值,所以可以对标记打标记!

于是我们将所有的+1标记按从右上到左下做差分,将所有的-1标记按从左上到右下做差分。

这样每个操作实际上只需要在两个数组中修改四个值,最后利用这两个数组还原原来的标记,利用标记还原原数阵。前缀和的计算也是类似,只不过第二维的前缀和要从两个方向各算一遍。

类似地,只需要4个数组就可以表示三维情况下的所有标记,经过三轮不同方向上的求前缀和就可以得到原四面体,前缀和的计算也需要4个数组。这样单操作/单询问要拆成8个,是O(1)的,复杂度是O(N^3+M+Q)。

ZCC Loves RPG

在程序的每一个位置,都存在若干条形如a[x]>=v或!(a[x]>=v)的限制。它们给每个a[i]确定了一个上界和一个下界。显然,我们应当让a[i]尽量小,因此我们可以令a[i]恰取到它的下界。

那么,考虑一对上下界数列Ui, Di,当前语句可以被执行到的条件就是:

2014 Multi-University Training Contest 2

随着我们分析程序的过程,一些位置的下界会发生变化,我们需要随时重新计算这个最大非零子段和,这就是说,我们需要对一个数列支持单点修改和查询最大非零子段和。这是一个线段树的经典应用,这里不再赘述。

下面考虑如何分析程序。

首先需要实现一个词法分析器:它接受字符串作为其输入,每次返回一个记号(常量,符号或标识符等)。这部分可以按照写读入优化的方法来写。

然后就到了对记号流进行分析的步骤,这一步的做法有很多,这里只讲一下std的做法。

首先将game(n, k)读入,获取n, k的值,同时建立线段树。

清空两个栈,这两个栈一个用于保存程序结构(称为栈P),一个用于保存Ui和Di的变化便于之后撤销(称为栈Q)。

读入左花括号,向栈P中压入一个B符号,B符号声明当前语句处于一对未闭合的花括号内,当栈P顶是B符号时,可以向这个花括号内追加语句。

随后重复以下过程直至栈P空。

检查栈P的栈顶:

若为B:从词法分析器取得下一个记号。

若为右花括号:弹出B。(闭合花括号)

否则:把这个记号“塞回去”,向栈P压入S符号。(追加语句)

若为S:弹出S,从词法分析器取得下一个记号。

若为左花括号:向栈P压入B符号。(新建块)

若为cg:读入整个cg语句,查询当前是否可达,更新答案。

若为if:读入if语句的第一句,读入x, v。向栈P先后压入I符号,T符号和S符号。向栈Q先后压入!(a[x]>=v)和a[x]>=v,在线段树上做a[x]>=v的修改。

若为T:弹出T,弹出栈Q栈顶元素,撤销它的修改。从词法分析器取得下一个记号:

若为else:应用栈Q栈顶元素(之前加入了但没有应用的!(a[x]>=v))的修改。向栈P压入S符号。

否则:把记号塞回去。弹出栈Q栈顶元素,弹出栈P栈顶元素(必定是I符号)。

若为I:弹出I,弹出栈Q栈顶元素,撤销它的修改。

B,S,T,I四个符号的意思分别是:块,语句,Then结束,整个If结束。

最后输出答案。

ZCC Loves Minecraft

所求的S即为当前点集的正交凸包。

参见en.wikipedia.org/wiki/Orthogonal_convex_hull的相关介绍。直观地看正交凸包是左上、右上、左下、右下四段折线构成的多边形,每条边都与坐标轴平行。求已知点集的正交凸包,可以先找出最上、最下、最左、最右的点。求右上方的一段折线,可以从最上面的点开始,每次找当前点右边的点中最上面的点,与当前点L形连接,作为新的当前点,不断重复直到选到最右边的点。这一过程实现非常简单,只要先按横坐标排序,然后扫描一遍并维护纵坐标最大值即可。其它三段的求法是类似的,而且可以通过旋转进行转化。那么对于动态加点的问题,可以用4棵平衡树(实现用STL即可)分别维护四段折线。插入点时尝试将其分别插入四段折线的相应位置,若在当前折线外部需要更新折线,并计算面积的增量。以右上折线为例,需要不断判断新点左边的点是否在新点下方,若是则删除。由于每个点最多被插入一次删除一次,总的时间复杂度是O(nlogn)。边界情况需要一定的特判。

ZCC Loves words

这道题我们可以先对单词建出AC自动机。然后在AC自动机上进行计数(dp)。F[i][j]表示当前长度第i位,当前在AC自动机的j号节点。我们考虑主动转移,如果j号节点在接受c这个字符后到了第k个节点,那么F[i+1][k]+=F[i][j]*Get。其中Get表示在第k个节点能乘上的数字。

我们来分析一下Get。2014 Multi-University Training Contest 2,那么如果没有+i这个东西,我们就可以建出矩阵,然后直接快速幂+矩阵乘法。但是有了+i的话,每个矩阵都不同,但是事实上P=179*173*163,这其中的质数非常的小,然后就是乱搞的部分了,i Mod Pi就是O(Pi)的循环,于是我们建出Pi个矩阵,然后对于L/Pi的部分快速幂+矩阵乘法,对于L Mod Pi的部分暴力矩阵乘法即可。对于每一个质数得到的答案,利用中国剩余定理就可算出最后的答案。复杂度大概是O(Pi*tot^3 + log(L)*tot^3)。

如果有更优的做法欢迎指教。

ZCC Loves cards

2014 Multi-University Training Contest 2

ZCC Love ranklist

第一问:

当k=1的时候答案显然为n-1。

当k=2t(t∈Z*)时答案显然为0。把比赛分成两两一组,每组和为n+1即可。

当k=2t+1(t∈Z*)时先两两一组到只剩3个。n为偶数时答案显然不能为0,只能做到1。奇数时则可以做到。一种解决办法是:

n为奇数

1

4

7

n为偶数

1

4

6

2

5

5

2

5

4

3

6

3

3

6

2

4

7

1

4

1

5

5

1

6

5

2

3

6

2

4

6

3

1

7

3

2

第二问:

先观察进步指数的性质:由于NewRank=OldRank的时候进步指数=0所以只能出现一次所以不应该使用。当NewRank!=OldRank的时候两个进步指数相等当且仅当NewRank和OldRank都相等,两个进步指数为相反数当且仅当NewRank1=OldRank2,OldRank1=NewRank2。

总共能出现n*(n-1)+1种不同的进步指数,而且n>1,所以每种进步指数(除了0)都会出现。并且进步指数为相反数的一对只能出现在同一场考试中。所以n为奇数时显然无解。

当n为偶数时,注意到每场考试实际上是将考生两两组队n-1轮且不重复。于是使用循环赛构造算法即可。

循环赛构造算法:http://www.doc88.com/p-694165485213.html

ZCC Love march

我们用set维护当前的士兵位置,合并的时候将每个士兵暴力合并到该位置,即删掉原位置士兵并使目标位置size+=原位置size。每次移动的时候将原位置size--并在新位置新建一个size为1的士兵。这样最多只会有n+移动数 的士兵,每个士兵只会合并一次,所以时间复杂度为O(nlogn)。实现的时候为了记录每个点的坐标可以用并查集维护所在的块。

ZCC Love traffic lights

首先考虑如果是一般图的话怎么做。可以证明存在一个最优解在某一条边上卡着时间进去或者卡着时间出来。因为如果不这样的话可以将每个点的时间往后推直到卡着时间。所以只要对在每个点上卡时的情况进行判断。

枚举了某个点的到达时间以后那么接下来我们可以使用最短路算法求出到终点的最早时间和从起点出发的最晚时间。具体实现的时候可以对每个点开8个状态记录从哪个方向进入和是否闯过红灯。

ZCC loves Army

Solution:

可以发现上司和下属之间的关系可以构成一棵树,考虑三种操作。

交换下属:为了使交换下属的时候改变的边变少,可以用左儿子右兄弟的方式表示这棵树。那么交换时只需要改变至多4条边。可以用LCT维护。

传送信息:求从x传信息到y所需要的中介人的最少个数。令x为深度较大的一个点,这条路径就是x向上传到和y同一层,再水平传送。可以把编号为1的儿子的权值赋值为1,求x,y简单路径上的权值和。

发送指令:求x可以收到几个士兵发出的指令。表现在左儿子右兄弟的树上即为求该点到根之间的点个数。

Postscript:

为了方便,可以给每个点加一个编号为0的虚孩子,可以避免一些细节问题。

交换下属的时候找孩子可以对每个点开一个平衡树,也可以直接用LCT里的Splay。

ZCC loves Codefires

考察序列中相邻的两题i, j(i在前)。交换它们后,解出它们之前的题目所带来的时间对答案的贡献是不变的,它们对它们后面的题目的贡献也是不变的,其他题目之间对答案的贡献自然也是不变的。唯一的变化就是,原来的EiKj一项变成了EjKi一项。那么,为了使答案变优,需要满足的条件是EjKi≤EiKj。也即Ei/Ki≥Ej/Kj。

那么,最优解序列Ai一定满足,EAi/KAi是递增的。

排序一遍即可。

ZCC Loves Intersection http://acm.hdu.edu.cn/showproblem.php?pid=4873

公式见题解。我没推。

 import java.math.BigInteger;
import java.util.Scanner; class Main{
public static void main(String arg[]){
Scanner cin=new Scanner(System.in);
BigInteger n,fenzi,fenmu,tmp,gcd;
int d;
while(cin.hasNext()){
n=cin.nextBigInteger();
d=cin.nextInt();
tmp=n.add(BigInteger.valueOf(4));
fenzi=tmp.multiply(tmp);
fenzi=fenzi.multiply(BigInteger.valueOf(d));
fenzi=fenzi.multiply(BigInteger.valueOf(d-1));
fenmu=n.pow(d);
fenmu=fenmu.multiply(BigInteger.valueOf(18));
gcd=fenmu.gcd(fenzi);
fenzi=fenzi.divide(gcd);
fenmu=fenmu.divide(gcd);
if(!fenzi.equals(fenmu)){
System.out.println(fenzi+"/"+fenmu);
}
else {
System.out.println(1);
}
}
}
}

ZCC loves cards http://acm.hdu.edu.cn/showproblem.php?pid=4876

二进制暴力加一个剪枝。勉强过。

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define mt(a,b) memset(a,b,sizeof(a))
using namespace std;
const int M=;
int a[M];
int b[M];
bool vis[M];
int sum[M];
int main(){
int n,k,L;
while(~scanf("%d%d%d",&n,&k,&L)){
for(int i=;i<n;i++){
scanf("%d",&a[i]);
}
int all=<<n;
int ans=;
for(int i=;i<all;i++){
int tmp=i;
int num=;
while(tmp){
if(tmp&) num++;
tmp>>=;
}
if(num==k){
int lb=;
for(int j=;j<n;j++){
if((i>>j)&){
b[lb++]=a[j];
}
}
int big=<<k;
mt(vis,);
for(int j=;j<big;j++){
int add=;
for(int u=;u<k;u++){
if((j>>u)&) add^=b[u];
}
vis[add]=true;
}
int R=;
for(int j=L;j<M;j++){
if(!vis[j]) break;
R=j;
}
if(ans>=R) continue; //cut
sort(b,b+lb);
do{
for(int j=;j<k;j++){
b[j+k]=b[j];
}
sum[]=b[];
int k2=k+k;
for(int j=;j<k2;j++){
sum[j]=sum[j-]^b[j];
}
mt(vis,);
for(int u=;u<k;u++){
for(int v=u;v<k2&&v-u<k;v++){
int add=sum[v];
if(u->=) add^=sum[u-];
vis[add]=true;
}
}
int R=;
for(int j=L;j<M;j++){
if(!vis[j]) break;
R=j;
}
ans=max(ans,R);
}while(next_permutation(b,b+lb));
}
}
printf("%d\n",ans);
}
return ;
}

ZCC loves march http://acm.hdu.edu.cn/showproblem.php?pid=4879

数据要离散化,并查集,有点吊。

 #include<cstdio>
#include<iostream>
#include<map>
#include<set>
using namespace std;
typedef __int64 LL;
typedef pair<LL,LL> pll;
const int mod=;
const int M=;
int f[M],sz[M],MXtot,MYtot,link[M];
LL lans;
struct Input {
LL x,y;
} a[M];
map<pll,LL>pos;
set<pll>PX[M],PY[M];
set<pll>::iterator it;
map<LL,int>MX,MY;
int getroot(int x) {
if(x==f[x])return x;
return f[x]=getroot(f[x]);
}
void ins(int id) {
pll now=make_pair(a[id].x,a[id].y);
if(pos[now]) {
sz[pos[now]]++;
f[id]=pos[now];
return;
}
pos[now]=id;
if(!MX[a[id].x]) MX[a[id].x]=++MXtot;
if(!MY[a[id].y]) MY[a[id].y]=++MYtot;
PX[MX[a[id].x]].insert(make_pair(a[id].y,id));
PY[MY[a[id].y]].insert(make_pair(a[id].x,id));
}
int main() {
int n,q,id;
LL m;
while(~scanf("%d%I64d",&n,&m)) {
for(int i=; i<=n; i++) {
f[i]=i;
sz[i]=;
link[i]=i;
}
for(int i=; i<=n; i++) {
scanf("%I64d%I64d",&a[i].x,&a[i].y);
ins(i);
}
scanf("%d",&q);
while(q--) {
char op[];
scanf("%s",op);
if(op[]=='Q') {
scanf("%d",&id);
id^=lans;
id=link[id];
int fa=getroot(id);
lans=;
LL X=a[fa].x;
LL Y=a[fa].y;
LL XX=MX[X];
LL YY=MY[Y];
LL pf,ps;
for(it=PX[XX].begin();it!=PX[XX].end();it++){
pf=it->first; //y
ps=it->second; //id
if(ps!=fa) {
LL dy=(pf-Y)%mod;
dy=dy*dy%mod;
lans=(lans+dy*sz[ps]%mod)%mod;
pos.erase(make_pair(X,pf));
PY[MY[pf]].erase(make_pair(X,ps));
sz[fa]+=sz[ps];
f[getroot(ps)]=fa;
}
}
PX[XX].clear();
PX[XX].insert(make_pair(Y,fa));
for(it=PY[YY].begin();it!=PY[YY].end();it++){
pf=it->first; //x
ps=it->second; //id
if(ps!=fa) {
LL dx=(pf-X)%mod;
dx=dx*dx%mod;
lans=(lans+dx*sz[ps]%mod)%mod;
pos.erase(make_pair(pf,Y));
PX[MX[pf]].erase(make_pair(Y,ps));
sz[fa]+=sz[ps];
f[getroot(ps)]=fa;
}
}
PY[YY].clear();
PY[YY].insert(make_pair(X,fa));
printf("%d\n",lans);
}
else {
LL y;
scanf("%d%I64d",&id,&y);
id^=lans;
int p=link[id];
sz[getroot(p)]--;
n++;
link[id]=n;
id=getroot(p);
a[n]=a[id];
f[n]=n;
sz[n]=;
pos.erase(make_pair(a[id].x,a[id].y));
if(op[]=='U') {
a[n].x-=y;
}
else if(op[]=='D'){
a[n].x+=y;
}
else if(op[]=='L'){
a[n].y-=y;
}
else if(op[]=='R'){
a[n].y+=y;
}
ins(n);
}
}
}
return ;
}

ZCC Loves Codefireshttp://acm.hdu.edu.cn/showproblem.php?pid=4882

推出一个优先关系,排序。

 #include<cstdio>
#include<algorithm>
using namespace std;
typedef __int64 LL;
const int M=;
struct G{
int t,k;
friend bool operator <(G a,G b){
return a.t*b.k<a.k*b.t;
}
}g[M];
int main(){
int n;
while(~scanf("%d",&n)){
for(int i=;i<n;i++){
scanf("%d",&g[i].t);
}
for(int i=;i<n;i++){
scanf("%d",&g[i].k);
}
sort(g,g+n);
LL ans=,tmp=;
for(int i=;i<n;i++){
ans+=(tmp+g[i].t)*g[i].k;
tmp+=g[i].t;
}
printf("%I64d\n",ans);
}
return ;
}

end