NOI 2018网络同步赛(游记?)
刚中考完那段时间比较无聊,报名了一个同步赛,报完名才发现成绩单是要挂到网上的,而且因为报的早给了一个很靠前的考号...那布星啊,赶紧学点东西,于是在一周内学了网络流,Treap以及一些数论。
Day1:
作为一个全国的信息学组织,官网竟然卡成这样,按理说OI选手应该学做网站也会简单一点吧...无法接受.jpg。终于还是在9点前拿到了题。浏览一遍题面感觉只有第一题比较可做,T2T3的暴力也不是很难打?
归程:https://www.luogu.org/problemnew/show/P4768
看到离线完全没有在意,写了一个最短路+BFS,50.本来还有10分可以写树剖,然而觉得写起来比较麻烦以至于没写。事实上离线的做法非常简单,最短路+并查集;关键是这个做法很有启发性,如果会写离线做法就离A题不远了。
T2写了next_permutation以及归并排序,期望得分8分,然而交错了题于是爆零。
T3写了4分的暴力,没有去重于是爆零。
于是Day1得分:50+0+0=50;
听有人说Cu线40,真是难以置信。
Day2:
因为Day1大爆炸,于是对Day2也没有什么希望。开题:XXX,XXX,多边形。emmmm多边形?计算几何吗?不过本来也没有打算做Day2T3于是愉快的开始看题啦。
第一眼感觉T1是个dp,T2网络流,T3...不知道是个什么...
仔细看T1,发现了一些奥妙重重的东西。“按照1-n的顺序”,顺序是固定的啊。“选择XXX的剑”,选剑也是固定的?必须一次把龙打死,不可以打好几个回合。那就不是dp了,好像没有什么决策之类的东西。使得x最小,二分!似乎并不满足单调性。
$$(a_i-atk_i*x)\%p_i=0$$
写出这个式子后仿佛发现了什么...看数据范围,“保证$p_i$是质数”,“lcm”,果然是个数论题目啊,感觉非常可做!
首先找一个set维护选哪些剑,然而set的各种操作非常不熟悉,调了好久决定手写一个Treap,此时大约10:00,还有三个半小时,难道连半道题都做不出来吗?
$$atk_i*x=a_i(\%p_i)$$
写了一个excrt(从网上抄的)解方程,然而似乎并不能解带系数的方程,难道是我把这道题想简单了吗?后来想到把$akt_i$的逆元乘过去就是普通的excrt啦。然而...P不是质数怎么办呢?一开始以为是无解,后来发现似乎并不是这样。可以把式子重新展开:
$$atk_i*x-a_i=p_i*y$$
可以把这里面的公因数全部除掉。看到这里你可能会觉得非常奇怪,因为除掉公因数的$p_i$有可能还是不是质数啊?逆元有两种求法,快速幂的方法肯定是不行了,但是扩欧还可以抢救一下。
Q:如果扩欧还是求不出逆元呢?A:这样就真的无解了。
再套用excrt,这道题就做完了。真的完成了吗,其实还没有。
这里面有几处乘法是爆longlong的,所以要用快速乘,另外也要注意只要是取模尽量往前提,和乘法合并在一起,比如说赛后改了一个小时的这个地方:
1 x=(a[i]-A)/d*x;
2 t=p[i]/d; 3 x=(x%t+t)%t;
如果这样写就会爆longlong,如果把第三行提到第一行边乘边取模就可以了。
顺便注意快速乘里面的左移应该写成这样:$a<<1LL$,每个常数都强转成longlong才保险。
其实还没有结束呢。对于任意的龙,我们还需要满足一个条件:$atk_i*x>=a_i$,不过这个地方还是比较简单的,因为这是个方程组,套用crt求最小整数的思想,任意解加减所有模数的最小公倍数后还是一个解,所以预处理一下把每条龙砍成非正数血的最小次数后取max,如果最终的答案小于这个值,就不停的加最小公倍数直到满足条件。
看起来好像不太难?然而我考试时提交的版本仅有35分,如果数组开到足够大可以有45分。这个AC思路我写了整整一天。也许,这个题对于NOI选手是个签到题?
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstdlib> 5 # define LL long long 6 # define inf (long long)(1e9) 7 8 inline LL min (LL x,LL y) { if(x>y) return y; return x; } 9 inline LL max (LL x,LL y) { if(x>y) return x; return y; } 10 int T,n,m,num; 11 const int maxn=1000005; 12 LL a[maxn],lcmm,minc,m1,m2,c1,c2; 13 LL p[maxn],ans; 14 LL g[maxn],x,exx,exy; 15 LL atk[maxn]; 16 17 LL mu(LL a,LL b,LL p) 18 { 19 a=(a%p+p)%p; 20 b=(b%p+p)%p; 21 LL res=0; 22 while(b) 23 { 24 if(b&1LL) res=(res+a)%p; 25 a=(a+a)%p; 26 b>>=1LL; 27 } 28 return res; 29 } 30 31 struct node 32 { 33 node *ch[2]; 34 LL v; 35 int r,s,n; 36 int cmp(LL x) 37 { 38 if(x==v) return -1; 39 if(x>v) return 1; 40 return 0; 41 } 42 void in(LL x) 43 { 44 v=x; 45 r=rand(); 46 s=n=1; 47 ch[0]=ch[1]=NULL; 48 } 49 void update() 50 { 51 s=n; 52 if(ch[0]) s+=ch[0]->s; 53 if(ch[1]) s+=ch[1]->s; 54 } 55 }*roo[6],pol[maxn<<3]; 56 57 node *newnode() 58 { 59 static int cnt=0; 60 return &pol[cnt++]; 61 } 62 63 void rotate(node *&n,int d) 64 { 65 node *k=n->ch[d^1]; 66 n->ch[d^1]=k->ch[d]; 67 k->ch[d]=n; 68 n->update(); 69 k->update(); 70 n=k; 71 } 72 73 void insert(node *&n,LL x) 74 { 75 if(!n) n=newnode(),n->in(x); 76 else 77 { 78 int d=n->cmp(x); 79 if(d==-1) ++n->n; 80 else 81 { 82 insert(n->ch[d],x); 83 if(n->ch[d]->r > n->r) rotate(n,d^1); 84 } 85 n->update(); 86 } 87 } 88 89 LL lef(node *&n,LL x) 90 { 91 if(!n) return -inf; 92 if(n->v>x) return lef(n->ch[0],x); 93 return max(n->v,lef(n->ch[1],x)); 94 } 95 96 LL rig(node *&n,LL x) 97 { 98 if(!n) return inf; 99 if(n->v<=x) return rig(n->ch[1],x); 100 return min(n->v,rig(n->ch[0],x)); 101 } 102 103 void del(node *&n,LL x) 104 { 105 if(!n) return ; 106 int d=n->cmp(x); 107 if(d==-1) 108 { 109 if(n->n>1) n->n--; 110 else 111 { 112 if(!n->ch[0]) n=n->ch[1]; 113 else if(!n->ch[1]) n=n->ch[0]; 114 else 115 { 116 int f=(n->ch[0]->r < n->ch[1]->r?0:1); 117 rotate(n,f); 118 del(n->ch[f],x); 119 } 120 } 121 } 122 else del(n->ch[d],x); 123 if(n) n->update(); 124 } 125 126 LL gcd(LL a,LL b) 127 { 128 if (!b) return a; 129 else return gcd(b,a%b); 130 } 131 132 void exgcd(LL a,LL b,LL &x,LL &y) 133 { 134 if (b==0) 135 x=1LL,y=0LL; 136 else 137 exgcd(b,a%b,y,x),y-=x*(a/b); 138 } 139 140 LL inv(LL v,LL p) 141 { 142 LL x,y,g; 143 exgcd(v,p,x,y); 144 g=gcd(x,y); 145 if (g>1) 146 return -1; 147 return (x%p+p)%p; 148 } 149 150 LL lcm (LL a,LL b) 151 { 152 return a/gcd(a,b)*b; 153 } 154 155 LL solve() 156 { 157 for (int i=1;i<=n;i++) 158 { 159 LL g=gcd(a[i],gcd(atk[i],p[i])); 160 atk[i]/=g,p[i]/=g,a[i]/=g; 161 LL Inv=inv(atk[i],p[i]); 162 if (Inv<0) 163 return -1LL; 164 a[i]=mu(a[i],Inv,p[i]); 165 } 166 LL M=p[1],A=a[1],t,d,x,y; 167 for(int i=2;i<=n;++i) 168 { 169 d=gcd(M,p[i]); 170 exgcd(M,p[i],x,y); 171 if((a[i]-A)%d) 172 return -1LL; 173 t=p[i]/d; 174 x=mu((a[i]-A)/d,x,t); 175 x=(x%t+t)%t; 176 A=(A+mu(M,x,(LL)M/d*p[i]))%((LL)M/d*p[i]); 177 M=M/d*p[i]; 178 } 179 A=(A%M+M)%M; 180 ans=A; 181 while (ans<minc) ans+=(LL)lcmm; 182 return ans; 183 } 184 185 int main() 186 { 187 scanf("%d",&T); 188 for (num=1;num<=T;num++) 189 { 190 minc=(LL)-1000; 191 scanf("%d%d",&n,&m); 192 for (int i=1;i<=n;++i) 193 scanf("%lld",&a[i]); 194 for (int i=1;i<=n;++i) 195 scanf("%lld",&p[i]); 196 for (int i=1;i<=n;++i) 197 scanf("%lld",&g[i]); 198 for (int i=1;i<=m;++i) 199 { 200 scanf("%lld",&x); 201 insert(roo[num],x); 202 } 203 if(n==1) lcmm=p[1]; 204 if(n>=2) lcmm=lcm(p[1],p[2]); 205 for (int i=3;i<=n;++i) 206 lcmm=lcm(lcmm,p[i]); 207 for (int i=1;i<=n;++i) 208 { 209 atk[i]=lef(roo[num],a[i]); 210 if(atk[i]<=0) atk[i]=rig(roo[num],(LL)-1000); 211 del(roo[num],atk[i]); 212 if(a[i]%atk[i]==0) minc=max(minc,a[i]/atk[i]); 213 else minc=max(minc,a[i]/atk[i]+(LL)1); 214 insert(roo[num],g[i]); 215 } 216 printf("%lld\n",solve()); 217 } 218 return 0; 219 }
这里强烈谴责T1的大样例,看起来非常庞大,检验性很强,然而我的35代码也可以过这个大样例。T2,T3其实弄点分不是很困难,但是T1写的太久了,最后就没开这两个题。
Day2得分:35+0+0=35;
于是这个比赛的得分:100(笔试)+50+35=185...Cu线199,于是就什么都没有啦。
实力不够是一个问题,但是还有一个问题就是考试的策略非常不合理。有5道题的正解确实是不会,但是唯一想到正解的题也没有写出高分来。
Day1的三道题都有不该丢的分,T1的65分离线其实想到了,但是因为已经写了一部分BFS舍不得扔掉就没写,5分的在线树剖其实也应该写一写(最后还剩很多时间),T2交错程序...,T3要是手出几组数据可能就想到判重的问题了。不过同步赛选手没有拿到大样例表示体验极差。T2有几个点其实相当于提答,但是也没有想到打打表。
Day2的T1挂到连暴力分都不如,首先数组只要开的下就尽量往大里开,如果不能保证正解能得分就写几个分段程序稳住分,n=1,m=1,p=1,p是质数的部分分其实都不难写吧。还有一些小一点的数据点其实可以枚举x。这样差不多就有70+了,T2T3连纯暴力都没写,听说2、30分的部分分也不是很难写。于是精神Cu。以后做题就有经验了,不要再犯这种错误啦,也要多学点东西。
---shzr