改题的过程中发现自己的思路有的很接近正解,但总是出了某些思维方式的偏差,所以或许还是写一写题解的好,毕竟游记都记的是一些乱七八糟的旅行经历和心理感受……正好中午饭前只有强行翘掉的一节课,就欣然划水了。
Day1
T1《小凯的疑惑》
得分:100
一道数学题,但是最合适的解法是打表找规律。记a为两个数中较小的一个,我打表的方式是对于每一个要check的数枚举$a$和$b$的系数暴力判断可行性,从$a+1$开始check,如果出现了连续的$a$个可行解就说明后面的每一个数$x$都可以由$(x-a)+1*a$得到,就不会再出现不可行解了。打出表来发现是个以$a-1$为公差的等差数列而$a$和$a+1$的答案是$a*(a+1)-a-(a+1)$,对拍了一下发现比较靠谱。实际上最简单的公式是$a*b-a-b$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 long long ai,bi,jy,ans; 7 int main() 8 { 9 //freopen("data.in","r",stdin); 10 //freopen("me.out","w",stdout); 11 freopen("math.in","r",stdin); 12 freopen("math.out","w",stdout); 13 scanf("%lld%lld",&ai,&bi); 14 if(ai==1||bi==1) 15 { 16 printf("0"); 17 fclose(stdin);fclose(stdout); 18 return 0; 19 } 20 if(ai>bi) 21 { 22 jy=bi; 23 bi=ai,ai=jy; 24 } 25 ans=ai*(ai+1)-(ai+ai+1); 26 ans+=(ai-1)*(bi-ai-1); 27 printf("%lld",ans); 28 fclose(stdin);fclose(stdout); 29 return 0; 30 }
T2《时间复杂度》
得分:100
一道中等的模拟题,比往年的T1难一点但是也没有难到大模拟的地步。主要应用stack,在每一个循环结束时都把状态还原为开始循环之前。我维护了这个循环对应的变量、复杂度是否为$n$、是否进入了循环。总复杂度就是读入复杂度。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<stack> 6 #include<algorithm> 7 using namespace std; 8 int ca,li,g,len1,nt,mx,tp,wz,zz; 9 bool h[30],op,w,jr,qn,hn; 10 char s1[20]; 11 stack<int> ha; 12 stack<int> fh; 13 stack<int> pr; 14 int main() 15 { 16 freopen("complexity.in","r",stdin); 17 freopen("complexity.out","w",stdout); 18 scanf("%d",&ca); 19 for(int i=1;i<=ca;i++) 20 { 21 scanf("%d%s",&li,s1); 22 len1=strlen(s1); 23 jr=1; 24 g=mx=op=w=nt=mx=0; 25 memset(h,0,sizeof(h)); 26 while(!pr.empty()) pr.pop(); 27 while(!ha.empty()) ha.pop(); 28 while(!fh.empty()) fh.pop(); 29 for(int j=0;j<len1;j++) 30 { 31 if(s1[j]>='0'&&s1[j]<='9') 32 g*=10,g+=s1[j]-'0'; 33 if(s1[j]=='n') op=1; 34 } 35 for(int j=1;j<=li;j++) 36 { 37 scanf("%s",s1); 38 if(s1[0]=='F') 39 { 40 scanf("%s",s1); 41 if(h[s1[0]-'a']) w=1; 42 h[s1[0]-'a']=1,fh.push(s1[0]-'a'); 43 pr.push(jr); 44 scanf("%s",s1); 45 if(s1[0]=='n') qn=1; 46 else 47 { 48 qn=wz=0; 49 len1=strlen(s1); 50 for(int k=0;k<len1;k++) 51 if(s1[k]>='0'&&s1[k]<='9') 52 wz*=10,wz+=s1[k]-'0'; 53 } 54 scanf("%s",s1); 55 if(s1[0]=='n') hn=1; 56 else 57 { 58 hn=zz=0; 59 len1=strlen(s1); 60 for(int k=0;k<len1;k++) 61 if(s1[k]>='0'&&s1[k]<='9') 62 zz*=10,zz+=s1[k]-'0'; 63 } 64 if((qn&&hn)||(!qn&&!hn)) ha.push(0); 65 if(!qn&&!hn&&wz>zz) jr=0; 66 if(qn&&!hn) jr=0,ha.push(0); 67 if(jr&&!qn&&hn) ha.push(1),nt++; 68 if(!jr&&!qn&&hn) ha.push(0); 69 if(nt>mx) mx=nt; 70 } 71 else 72 { 73 if(fh.empty()){ w=1;continue; } 74 tp=fh.top(),fh.pop(); 75 h[tp]=0; 76 tp=ha.top(),ha.pop(); 77 if(tp==1) nt--; 78 jr=pr.top(),pr.pop(); 79 } 80 } 81 if(!fh.empty()) w=1; 82 if(w==1) 83 { 84 printf("ERR "); 85 continue; 86 } 87 if((op==0&&mx==0)||(op==1&&mx==g)) 88 { 89 printf("Yes "); 90 continue; 91 } 92 printf("No "); 93 } 94 fclose(stdin);fclose(stdout); 95 //while(1); 96 return 0; 97 }
T3《逛公园》
得分:10
在考场上就没有足够的信心A掉这题,所以即使前两题稳而且时间充足也没有坚持去想正解,出现了问题没有积极想怎样去解决。以后要避免这种问题,考试策略应该主要根据题目的情况而不是经验,特别是决定弃掉某题应该仔细斟酌。当时主要卡在两个地方,一个是环的判断(实际上我几乎一开始就弃掉了这$30$分想都没想),另一个是合理的状态转移。考场上脑子一抽认为最短路的复杂度就没有$n^2$以下的,所以不知道怎么办。堆优化迪杰斯特拉的复杂度可以达到大概$nlogn$。状态数组是正确的,$f[i][j]$表示到$i$点距离为最短+$j$的方案数。用$dis[i]$表示到$i$的最短路长度,当时写的转移方程$f[v][dis[u]+w+j-dis[v]]=sum{f[u][j]}$,而且用的转移顺序是奇奇怪怪的spfa序,相当于再写了一遍spfa。实际上正确的转移顺序是按照拓扑序记忆化搜索,而且正确的转移方程是$f[u][j]=sum{f[v][j-(dis[u]+w-dis[v])]}$。对于环的判断,用$0$边跑tarjan缩点,check每一个$0$环里的点到$1$和到$n$的最短路之和是否小于等于$dis[n]+k$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<stack> 6 #define ll long long 7 #define mk make_pair 8 using namespace std; 9 inline int read() 10 { 11 int jg=0,jk=getchar()-'0'; 12 while(jk<0||jk>9) jk=getchar()-'0'; 13 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 14 return jg; 15 } 16 const int sj=100010; 17 int n,m,k,p,ca,dis1[sj],disn[sj],h[sj],e,a1,a2,a3,o,d[sj]; 18 int dfn[sj],low[sj],bl[sj],size[sj]; 19 bool r[sj],op,ye; 20 ll f[sj][52],ans; 21 struct B 22 { 23 int ne,v,w; 24 }b[sj<<1],c[sj<<1]; 25 void add(int x,int y,int z) 26 { 27 b[e].v=y,b[e].w=z,b[e].ne=h[x],h[x]=e++; 28 c[o].v=x,c[o].w=z,c[o].ne=d[y],d[y]=o++; 29 } 30 typedef pair<int,int> di; 31 priority_queue<di , vector<di> , greater<di> > q; 32 stack<int> s; 33 void dij() 34 { 35 while(!q.empty()) q.pop(); 36 memset(r,0,sizeof(r)); 37 dis1[1]=0; 38 q.push(mk(0,1)); 39 while(!q.empty()) 40 { 41 a1=q.top().second,q.pop(); 42 if(r[a1]) continue; 43 r[a1]=1; 44 for(int i=h[a1];i!=-1;i=b[i].ne) 45 if(dis1[b[i].v]>dis1[a1]+b[i].w) 46 { 47 dis1[b[i].v]=dis1[a1]+b[i].w; 48 q.push(mk(dis1[b[i].v],b[i].v)); 49 } 50 } 51 while(!q.empty()) q.pop(); 52 memset(r,0,sizeof(r)); 53 disn[n]=0; 54 q.push(mk(0,n)); 55 while(!q.empty()) 56 { 57 a1=q.top().second,q.pop(); 58 if(r[a1]) continue; 59 r[a1]=1; 60 for(int i=d[a1];i!=-1;i=c[i].ne) 61 if(disn[c[i].v]>disn[a1]+c[i].w) 62 { 63 disn[c[i].v]=disn[a1]+c[i].w; 64 q.push(mk(disn[c[i].v],c[i].v)); 65 } 66 } 67 } 68 void init() 69 { 70 n=read(),m=read(),k=read(),p=read(); 71 memset(dis1,0x7f,sizeof(dis1)); 72 memset(disn,0x7f,sizeof(disn)); 73 memset(h,-1,sizeof(h));memset(d,-1,sizeof(d)); 74 memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low)); 75 memset(f,-1,sizeof(f));memset(size,0,sizeof(size)); 76 e=o=ans=op=ye=0; 77 for(int i=1;i<=m;i++) 78 { 79 a1=read(),a2=read(),a3=read(); 80 add(a1,a2,a3); 81 if(!a3) ye=1; 82 } 83 } 84 inline void bj(int &x,int y) 85 { 86 x=x<y?x:y; 87 } 88 void tarjan(int x) 89 { 90 dfn[x]=low[x]=++a1; 91 r[x]=1,s.push(x); 92 for(int i=h[x];i!=-1;i=b[i].ne) 93 { 94 if(b[i].w) continue; 95 if(!dfn[b[i].v]) tarjan(b[i].v),bj(low[x],low[b[i].v]); 96 else if(r[b[i].v]) bj(low[x],dfn[b[i].v]); 97 } 98 if(low[x]==dfn[x]) 99 { 100 a3++; 101 do 102 { 103 a2=s.top(),s.pop(); 104 r[a2]=0; 105 bl[a2]=a3,size[a3]++; 106 }while(a2!=x); 107 } 108 } 109 void check() 110 { 111 memset(r,0,sizeof(r)); 112 a1=a2=a3=0; 113 while(!s.empty()) s.pop(); 114 for(int i=1;i<=n;i++) 115 if(!dfn[i]) 116 tarjan(i); 117 for(int i=1;i<=n;i++) 118 if(size[bl[i]]!=1&&dis1[i]+disn[i]<=dis1[n]+k) 119 op=1; 120 } 121 ll dp(int x,int aim) 122 { 123 if(x==n&&aim==0) f[x][aim]=1; 124 if(f[x][aim]!=-1) return f[x][aim]; 125 f[x][aim]=0; 126 for(int i=h[x];i!=-1;i=b[i].ne) 127 if(aim-(dis1[x]+b[i].w-dis1[b[i].v])>=0) 128 { 129 dp(b[i].v,aim-(dis1[x]+b[i].w-dis1[b[i].v])); 130 f[x][aim]=(f[x][aim]+f[b[i].v][aim-(dis1[x]+b[i].w-dis1[b[i].v])])%p; 131 } 132 return f[x][aim]; 133 } 134 int main() 135 { 136 ca=read(); 137 for(int l=1;l<=ca;l++) 138 { 139 init(); 140 dij(); 141 if(ye) check(); 142 if(op) printf("%d ",-1); 143 else 144 { 145 for(int i=0;i<=k;i++) dp(1,i); 146 for(int i=0;i<=k;i++) ans=(ans+f[1][i])%p; 147 printf("%lld ",ans); 148 } 149 } 150 return 0; 151 }
Day2
T1《奶酪》
得分:100
如果说Day1T2是在考stack,那么这题就是在考queue了?应该存在无限多种$n^2$解法,我是用bfs实现的。队列一开始放所有从底面能直接到达的球,不断从队首扩展和它相连的球,直到队列空或到达了一个能到达上顶面的球。需要稍微注意一下的就是用半径平方代替距离开根,而且距离的平方可能会炸long long,半径却不会炸,如果出负数可以直接判不可能(在极限数据下,或许会出现这种情况)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<algorithm> 6 #define ll long long 7 using namespace std; 8 const int sj=1010; 9 struct ch 10 { 11 ll xm,ym,zm; 12 }c[sj]; 13 int ca,n,tp; 14 ll h,r,dis,nt,d1,d2,d3; 15 bool vi[sj],op; 16 queue<int> q; 17 int main() 18 { 19 freopen("cheese.in","r",stdin); 20 freopen("cheese.out","w",stdout); 21 scanf("%d",&ca); 22 for(int l=1;l<=ca;l++) 23 { 24 scanf("%d%lld%lld",&n,&h,&r); 25 memset(vi,0,sizeof(vi)); 26 while(!q.empty()) q.pop(); 27 op=0; 28 for(int i=1;i<=n;i++) 29 { 30 scanf("%lld%lld%lld",&c[i].xm,&c[i].ym,&c[i].zm); 31 if(c[i].zm<=r) vi[i]=1,q.push(i); 32 } 33 dis=4*r*r; 34 while(!q.empty()) 35 { 36 tp=q.front(),q.pop(); 37 if(c[tp].zm>=h-r){ op=1;break; } 38 for(int i=1;i<=n;i++) 39 if(!vi[i]) 40 { 41 d1=(c[i].xm-c[tp].xm)*(c[i].xm-c[tp].xm); 42 if(d1>dis) continue; 43 d2=(c[i].ym-c[tp].ym)*(c[i].ym-c[tp].ym); 44 if(d2>dis) continue; 45 if(d1+d2>dis) continue; 46 d3=(c[i].zm-c[tp].zm)*(c[i].zm-c[tp].zm); 47 if(d3>dis) continue; 48 nt=d1+d2+d3; 49 if(nt>=0&&nt<=dis) vi[i]=1,q.push(i); 50 } 51 } 52 if(op==1) printf("Yes "); 53 else printf("No "); 54 } 55 fclose(stdin);fclose(stdout); 56 //while(1); 57 return 0; 58 }
T2《宝藏》
得分:70
明显的状压DP,因为调不出状压直接写了dfs枚举生成树,居然也拿了70分,其实根本不清楚它的复杂度。在家里看了meaty学长的题解,觉得可以理解就写了写,思路并没有复杂到想不出来的地步。$f[i][j][k]$表示$i$节点深度为$j$子树中点的状态为$k$的最优解,按照从儿子向父节点合并的思路转移,$f[i][j][k]=min(f[son][j+1][t]+f[i][j][k-t]+j*w)$。按照DP优化的常见思路把与k无关的部分提出来,设$g[i][j][k]=min(f[son][j+1][t]+j*w)$,则$f[i][j][k]=g[i][j][k]+min(f[i][j][k-t])$(这里的减法表示集合的异或),就从$n^3*3^n$优化到了$n^2*3^n$。优化部分并不困难,但是考试的时候连基本的DP都没写出来,一门心思地枚举子集,忽略了它是一个树上状压应该同时有合并子树的思路。状压DP做得不算少,树DP更是每天都在写,两者结合之后还是没能灵活使用,有考试中急于求成的原因,但更多的是自己实力还不够强。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define bit(x) (1<<((x)-1)) 6 using namespace std; 7 const int sj=(1<<12)+5; 8 int n,m,w[15][15],f[15][15][sj],g[15][15][sj],a1,a2,a3; 9 inline int read() 10 { 11 int jg=0,jk=getchar()-'0'; 12 while(jk<0||jk>9) jk=getchar()-'0'; 13 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 14 return jg; 15 } 16 inline void bj(int &x,int y) 17 { 18 x=x<y?x:y; 19 } 20 int main() 21 { 22 n=read(),m=read(); 23 memset(w,0x7f,sizeof(w)); 24 memset(f,63,sizeof(f)); 25 memset(g,63,sizeof(g)); 26 for(int i=1;i<=m;i++) 27 { 28 a1=read(),a2=read(),a3=read(); 29 if(w[a1][a2]>a3) w[a1][a2]=w[a2][a1]=a3; 30 } 31 a3=(1<<n)-1,a1=w[0][0]; 32 for(int i=1;i<=n;i++) 33 for(int j=1;j<=n;j++) 34 f[i][j][bit(i)]=0; 35 for(int j=n-1;j>0;j--) 36 for(int s=0;s<=a3;s++) 37 for(int i=1;i<=n;i++) 38 { 39 for(int k=1;k<=n;k++) 40 if((bit(k)|s)==s&&w[i][k]<a1) 41 bj(g[i][j][s],f[k][j+1][s]+w[i][k]*j); 42 for(int t=s&(s-1);t;(--t)&=s) 43 bj(f[i][j][s],f[i][j][s^t]+g[i][j][t]); 44 } 45 for(int i=1;i<=n;i++) bj(a1,f[i][1][a3]); 46 printf("%d",a1); 47 return 0; 48 }
T2《列队》
得分:50
其实这道题是没有好好做,因为是写T2调不出来了临时写了一会T3保暴力分,后来写完了T2又回来写了另一个部分分(没开long long挂掉10分,很服气jiry居然还设置了这个细节检验暴力选手的细心程度),从始至终都没有仔细想T3到底应该怎么做;“数据结构”这四个字更是没有想到过,写出了树状数组的部分分却没有想这种做法应该怎么推广,而线段树平衡树……据说不在NOIP考纲内?不过改了题之后感觉这个做法确实以我的数据结构水平(就没有吧)来看需要写很久,想不到或许是我的幸运呢。对于每一行用一个线段树维护$m-1$个位置,每次找到询问的那个数并把它删去;再用一个线段树维护最后一列,找到会进入这一行的数把它加进这一行并从最后一列删除,再把出队的人加在这个线段树末尾。每个线段树都要维护$max(n,m)+q$个位置,动态开点是必须的,把数加在线段树末尾可以直接对每棵线段树开一个vector。至于找区间里要求的数和删除操作,可以用1表示删去,每次统计一下剩下的数的个数($mid-l+1-t[lc].sum$),就是平常的线段树上二分了。尽管线段树比平衡树跑得快很多,还是可能被CCF老爷机卡常,然而也不会别的做法了。说起来联赛……所有卡常题都没有想到正解;虽然说是“出题人卡我常”,换个角度来想也可以说是“出题人要求我写出更优秀的算法”吧。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 #define ll long long 6 #define mid (l+r>>1) 7 using namespace std; 8 inline ll read() 9 { 10 ll jg=0; 11 int jk=getchar()-'0'; 12 while(jk<0||jk>9) jk=getchar()-'0'; 13 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 14 return jg; 15 } 16 const int sj=300010; 17 ll n,m; 18 int rt[sj],sz,mx,q,a1,a2; 19 struct tree 20 { 21 int sum,lc,rc; 22 }t[sj*100]; 23 vector<ll> re[sj]; 24 void update(int &x,int l,int r,int v) 25 { 26 if(!x) x=++sz; 27 t[x].sum++; 28 if(l<r) 29 { 30 if(v<=mid) update(t[x].lc,l,mid,v); 31 else update(t[x].rc,mid+1,r,v); 32 } 33 } 34 int query(int x,int l,int r,int v) 35 { 36 if(l==r) return l; 37 ll siz=mid-l+1-t[t[x].lc].sum; 38 if(siz>=v) return query(t[x].lc,l,mid,v); 39 else return query(t[x].rc,mid+1,r,v-siz); 40 } 41 ll getl(int x,ll y) 42 { 43 int aim=query(rt[n+1],1,mx,x); 44 update(rt[n+1],1,mx,aim); 45 ll ret; 46 if(aim<=n) ret=aim*m; 47 else ret=re[n+1][aim-n-1]; 48 re[n+1].push_back(y?y:ret); 49 return ret; 50 } 51 ll geth(int x,int y) 52 { 53 int aim=query(rt[x],1,mx,y); 54 update(rt[x],1,mx,aim); 55 ll ret; 56 if(aim<m) ret=(x-1)*m+aim; 57 else ret=re[x][aim-m]; 58 re[x].push_back(getl(x,ret)); 59 return ret; 60 } 61 int main() 62 { 63 n=read(),m=read(),q=read(); 64 mx=n+q; 65 if(m+q>mx) mx=m+q; 66 for(int i=1;i<=q;i++) 67 { 68 a1=read(),a2=read(); 69 if(a2!=m) printf("%lld ",geth(a1,a2)); 70 else printf("%lld ",getl(a1,0)); 71 } 72 return 0; 73 }