2021“MINIEYE杯”中国大学生算法设计超级联赛(4)游记&题解
游记
这次居然网站没卡好评。
进去先看了1011感觉没有看懂(
后来看到了一堆人切掉了1001就去看1001结果一堆高数内容。就扔给了cqy
然后看到1002发现是个sb题就切掉了。
cqy说1009是AT678然后去看了一下。
发现是sb题就切掉了。
然后去看1004因为觉得很可做,这种最小值一眼二分,然后看到要去重就写了个SA,乱写了个height就过了样例。
然后交上去一直WA
迫不得已上对拍,然后发现多测不清空。
交上去就过了。
这时候突然想到1011不就是最小斯坦纳树的两倍吗?直接莫队就好了吧,反正有15s
发现没看到T组数据,然后1.5s的\(O(n\sqrt nlogn)\)似乎很危。
写了一发交上去果然WA了。
然后就直接卡常卡到结束。
题解
1002
以每个点为根直接dfs一边算答案不就好了。
code:
using namespace std;
I void read(int &x){char s=Gc();x=0;while(s<'0'||s>'9') s=Gc();while(s>='0'&&s<='9') x=x*10+s-48,s=Gc();}
int T,n,m,x,W[N+5],F[N+5],D[N+5],Ans;ll ToT1,ToT2,B1[N+5],B2[N+5];
struct yyy{int to,z;};const ll Bas=19560929;
struct ljb{int head,h[N+5];yyy f[N+5<<1];I void add(int x,int y){f[++head]=(yyy){y,h[x]};h[x]=head;}}s;
I void dfs(int x,int last){
Ans+=!F[W[x]];F[W[x]]++;D[x]=Ans;yyy tmp;for(int i=s.h[x];i;i=tmp.z) tmp=s.f[i],tmp.to^last&&(dfs(tmp.to,x),0);F[W[x]]--;Ans-=!F[W[x]];
}
I void Solve(){
Me(s.h,0);s.head=0;re int i,j;scanf("%d",&n);for(i=2;i<=n;i++) scanf("%d",&x),s.add(x,i),s.add(i,x);for(i=1;i<=n;i++) scanf("%d",&W[i]);
for(i=1;i<=n;i++){
dfs(i,i);for(ToT1=ToT2=0,j=1;j<=n;j++) ToT1=(ToT1+B1[j-1]*D[j])%mod1,ToT2=(ToT2+B2[j-1]*D[j])%mod2;printf("%lld %lld\n",ToT1,ToT2);
}
}
int main(){
freopen("1.in","r",stdin);
re int i;B1[0]=B2[0]=1;for(i=1;i<N;i++) B1[i]=B1[i-1]*Bas%mod1,B2[i]=B2[i-1]*Bas%mod2;scanf("%d",&T);while(T--) Solve();
}
1009
后面6个数字和字母直接爆搜连通块即可因为都是连在一起的。
但是前面的汉字并不联通。
观察可得所有汉字的宽都是14或15,那么从左往右找到第一个有值的然后搜15个即可。
code:
using namespace std;
I void read(int &x){char s=Gc();x=0;while(s<'0'||s>'9') s=Gc();while(s>='0'&&s<='9') x=x*10+s-48,s=Gc();}
int T,n=30,m=100,Maxn,Minn,fl[35][105];char S[35][105];
int xp[4]={1,-1,0,0};
int yp[4]={0,0,1,-1};
I void dfs(int x,int y){
fl[x][y]=1,Maxn=max(Maxn,y);Minn=min(Minn,y);int nowx,nowy,i;for(i=0;i<4;i++) nowx=x+xp[i],nowy=y+yp[i],S[nowx][nowy]&&!fl[nowx][nowy]&&(dfs(nowx,nowy),0);
}
I void Solve(int T){
Me(fl,0);re int i,j;for(i=1;i<=n;i++)for(scanf("%s",S[i]+1),j=1;j<=m;j++) S[i][j]=(S[i][j]=='#');printf("Case #%d:\n",T);
Minn=1e9;for(i=1;i<=n;i++) for(j=1;j<=m;j++) S[i][j]&&(Minn=min(Minn,j));
for(Maxn=0,i=Minn;i<=Minn+14;i++)for(j=1;j<=n;j++)S[j][i]&&(Maxn=max(Maxn,i)); printf("%d %d\n",Minn,Maxn);
for(i=Maxn+1;i<=m;i++){
for(j=1;j<=n;j++) !fl[j][i]&&S[j][i]&&(Maxn=Minn=i,dfs(j,i),printf("%d %d\n",Minn,Maxn));
}
}
int main(){
freopen("1.in","r",stdin);
re int i;scanf("%d",&T);for(i=1;i<=T;i++) Solve(i);
}
1004
二分答案,然后转化成判定性问题。
因为这道题要去重,所以先把SA跑出来,然后把height数组求出来。
对于一个递增的左端点来说,区间小于等于一个定值的右端点是递增的。所以这个可以双指针,然后去掉height的贡献即可。
时间复杂度\(O(nlogn+nlogw)\)
code:
using namespace std;
I void read(int &x){char s=Gc();x=0;while(s<'0'||s>'9') s=Gc();while(s>='0'&&s<='9') x=x*10+s-48,s=Gc();}
int T,l,r,mid,n,m,W[N+5],Sum[N+5],sa[N+5],rk[N+5],h[N+5],F[N+5];char S[N+5];ll k;
struct ques{int x,y,id;}P[N+5],A[N+5];
I void SA(){
re int i,j;Me(F,0);for(i=1;i<=n;i++) F[S[i]]++;for(i=1;i<=128;i++) F[i]+=F[i-1];for(i=1;i<=n;i++) rk[i]=F[S[i]];
for(i=1;i<=n;i<<=1){
for(j=1;j<=n;j++) P[j]=(ques){rk[j],j+i<=n?rk[j+i]:0,j};
Me(F,0);for(j=1;j<=n;j++) F[P[j].y]++;for(j=1;j<=n;j++) F[j]+=F[j-1];for(j=n;j;j--) A[F[P[j].y]--]=P[j];
Me(F,0);for(j=1;j<=n;j++) F[A[j].x]++;for(j=1;j<=n;j++) F[j]+=F[j-1];for(j=n;j;j--) P[F[A[j].x]--]=A[j];
for(j=1;j<=n;j++)rk[P[j].id]=rk[P[j-1].id]+(P[j].x^P[j-1].x||P[j].y^P[j-1].y);if(rk[P[n].id]==n) break;
}for(i=1;i<=n;i++) sa[rk[i]]=i;
}
I ll check(int mid){
re int i,j;ll Ans=0;for(i=j=1;i<=n;i++){
while(j<=n&&Sum[j]-Sum[i-1]<=mid) j++;Ans+=max(0,j-i-h[i]);//if(mid==6) printf("%lld\n",Ans);
}return /*printf("%d %lld\n",mid,Ans),*/Ans;
}
I void Solve(){
Me(h,0);re int i;scanf("%d%lld",&n,&k);scanf("%s",S+1);for(i=1;i<=26;i++) scanf("%d",&W[i]);for(i=1;i<=n;i++) Sum[i]=Sum[i-1]+W[S[i]-'a'+1];SA();
for(i=1;i<=n;i++){
if(rk[i]==1) continue;h[i]=max(h[i-1]-1,0);while(i+h[i]<=n&&sa[rk[i]-1]+h[i]<=n&&S[i+h[i]]==S[sa[rk[i]-1]+h[i]])h[i]++;
}l=-1;r=1e8;while(l+1<r) mid=l+r>>1,(check(mid)>=k?r:l)=mid;printf("%d\n",(r==1e8)?-1:r);
}
int main(){
freopen("1.in","r",stdin);
scanf("%d",&T);while(T--) Solve();
}
其他题目咕咕咕。