2021“MINIEYE杯”中国大学生算法设计超级联赛(3) 游记&题解
游记
12:00去开题,然后开了个锤子。
网站疯狂卡崩。
直到12:50才进去。
为什么有的队伍开局6min就切题了啊
看到1011很多人过了就去看。
然后发现并不会(
想了一下决定暴力硬刚,写了个map+队列维护每一层的垃圾玩意,交上去WA了。
然后转眼一看这不直接记搜就好了嘛?我是小丑。
然后发现居然和cqy同时过了,他写了1007
然后再看了一眼榜,发现1004蛮多人过了就去看,发现似乎就是随便算平行直线就好了。写了一发WA了发现没判平行y轴,再改了一发交上去过了。
cqy觉得1009很可做因为随机,然后一手翻译成价格和宝石选一个,然后死都不知道数据随机怎么用
看了一下榜发现1003过的人也很多,看了一眼感觉FFT匹配然后又不像,之后发现枚举字符真的就是FFT匹配,NTT时模数写成982244353也没谁了。
漫无目的开了1010,发现这不就是wqs二分经典题吗,然后写了一发发现T了,发现可以爆枚所有斜率,然后交上去还是T了,发现大概要2e9次运算,就死命卡常没过。
想不到什么\(O(nlogn+m)\)的MST
然后就4题11h排到了162,如果罚时少200min就可以到120,垃圾服务器毁我青春
题解
1011
容易发现对于一个给定的\(k\),\(n\)的答案是恒定的。
又因为线段树的第\(i\)层只有\(i\)个节点。
所以直接记搜复杂度正确。
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;ll n,k,Ans,ToT;map<ll,ll> G;
I ll dfs(ll n){
if(n<=k) return 1;if(G.count(n)) return G[n];return G[n]=dfs(n>>1)+dfs(n+1>>1)+1;
}
int main(){
freopen("1.in","r",stdin);
scanf("%d",&T);while(T--){
scanf("%lld%lld",&n,&k);G.clear();printf("%lld\n",dfs(n));
}
}
1004
容易发现如果我们先将所有的斜率都选一遍,再将剩下还有的斜率选一边,这样重复是最优的。
那么直接开个map记录即可。注意特判斜率为无穷的情况。时间复杂度\(O(nlogn)\)
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,nx,mx,ny,my,cnt,F[N+5],Maxn,nows;map<db,int> G;pair<db,int> pus;
int main(){
freopen("1.in","r",stdin);
re int i,j;scanf("%d",&T);while(T--){
scanf("%d",&n);G.clear();cnt=nows=Maxn=0;for(i=1;i<=n;i++) scanf("%d%d%d%d",&nx,&ny,&mx,&my),mx==nx?(nows++):(G[(my-ny)*1.0/(mx-nx)]++);
Me(F,0);for(map<db,int>::it i=G.begin();i!=G.end();i++) pus=*i,F[pus.second]++,Maxn=max(Maxn,pus.second);F[nows]++;Maxn=max(Maxn,nows);
for(i=Maxn;i;i--) F[i]+=F[i+1];
for(i=1;i<=Maxn;i++){
while(F[i]--) cnt++,printf("%d\n",cnt-i);
}
}
}
1003
思路肯定是算出对于一个点以后的\(m\)个点有几个匹配然后扔到答案数组上前缀和一下就好了。
我们顺着这个思路下去。
带通配符的匹配一般就是FFT的匹配。
我们考虑枚举每个字符,算它匹配多少次然后加起来即可。
构造一下发现如果我们将所有通配符和当前字符都是\(1\),那么两边相乘就是匹配个数。
NTT加法卷积即可。时间复杂度\(O(wnlogn)\)
code:
using namespace std;
int T,n,m,tr[N+5],k,cnt,F[N+5],H[N+5];char s1[N+5],s2[N+5];ll A[N+5],B[N+5];
I ll mpow(ll x,int y=mod-2){ll ans=1;while(y) (y&1)&&(ans=ans*x%mod),x=x*x%mod,y>>=1;return ans;}
I void swap(ll &x,ll &y){x^=y^=x^=y;}const ll G=3,invG=mpow(G);
I void NTT(ll *A,int n,int flag){
int i,j,h;ll pus,now,key;for(i=0;i<n;i++) i<tr[i]&&(swap(A[i],A[tr[i]]),0);
for(i=2;i<=n;i<<=1){
for(key=mpow(flag?G:invG,(mod-1)/i),j=0;j<n;j+=i) {
for(now=1,h=j;h<j+i/2;h++) pus=A[h+i/2]*now%mod,A[h+i/2]=(A[h]-pus+mod)%mod,A[h]=(A[h]+pus)%mod,now=now*key%mod;
}
}if(flag)return;ll inv=mpow(n);for(i=0;i<n;i++) A[i]=A[i]*inv%mod;
}
int main(){
freopen("1.in","r",stdin);
re int i,j;scanf("%d",&T);while(T--){
scanf("%d%d",&n,&m);Me(H,0);Me(F,0);scanf("%s",s1);scanf("%s",s2);for(k=1;k<=n+m;k<<=1);for(i=0;i<k;i++) tr[i]=(tr[i>>1]>>1)|((i&1)?k/2:0);
for(i=cnt=0;i<m;i++) cnt+=(s2[i]=='*');for(i=0;i<=9;i++){
Me(A,0);Me(B,0);for(j=0;j<n;j++) A[j]=(s1[j]==48+i||s1[j]=='*');for(j=0;j<m;j++) B[m-j-1]=(s2[j]==48+i);
NTT(A,k,1);NTT(B,k,1);for(j=0;j<k;j++) A[j]=A[j]*B[j]%mod;NTT(A,k,0);for(j=m-1;j<n;j++) F[j-m+1]+=A[j];
}
for(i=0;i<=n-m;i++) H[m-cnt-F[i]]++;for(i=1;i<=m;i++)H[i]+=H[i-1];for(i=0;i<=m;i++) printf("%d\n",H[i]);
}
}
1010
我是sb
首先这个东西是个经典题目,因为这个函数是凸的,所以可以直接wqs二分,时间复杂度\(O(Tnmlogn)\)
然后发现斜率很小可以直接爆枚,然后就是\(O(Tnm)\)
但是还是不行,考虑怎么将边数降为\(O(n)\)
容易发现我们跑折扣边和非折扣边两颗MST出来,不在这上面的边肯定不会出现。
所以复杂度变成\(O(T(m+n^2))\)
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 n,m,k,Maxn,fa[N+5],T,Ans[N+5],un,wn,Gh;struct ques{int w,a,b,x,y;}S[M+5],G[N+5<<1],now;vector<ques> F1[N+5<<1],F2[N+5<<1],F3[N+5<<1];
I bool cmp1(ques x,ques y){return x.a<y.a;}I bool cmp2(ques x,ques y){return x.b<y.b;}
I int Getfa(int x){return x==fa[x]?x:fa[x]=Getfa(fa[x]);}
I void GetAns(int mid){
re int i,j,cntmax=0,cntmin=0,ans=0,ToT=0,tots=0;for(i=1;i<=n;i++) fa[i]=i;for(i=1;i<=Maxn*2;i++) F1[i].clear(),F2[i].clear(),F3[i].clear();
for(i=1;i<=Gh;i++){
G[i].w=min(G[i].b,G[i].a+mid);ToT=max(ToT,G[i].w);
if(G[i].a+mid==G[i].b)F1[G[i].w].push_back(G[i]);
else if(G[i].a+mid<G[i].b) F2[G[i].w].push_back(G[i]);
else F3[G[i].w].push_back(G[i]);
}
for(i=1;i<=ToT;i++){
int s1=F1[i].size(),s2=F2[i].size(),s3=F3[i].size();
for(j=0;j<s1;j++) {
now=F1[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,fa[un]=wn);
}
for(j=0;j<s3;j++) {
now=F3[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,fa[un]=wn);
}
for(j=0;j<s2;j++) {
now=F2[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,cntmin++,fa[un]=wn);
}
}
for(i=1;i<=n;i++) fa[i]=i;tots=ans=0;
for(i=1;i<=ToT;i++){
int s1=F1[i].size(),s2=F2[i].size(),s3=F3[i].size();
for(j=0;j<s1;j++) {
now=F1[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,cntmax++,fa[un]=wn);if(tots==n-1) break;
}
for(j=0;j<s2;j++) {
now=F2[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,cntmax++,fa[un]=wn);if(tots==n-1) break;
}
for(j=0;j<s3;j++) {
now=F3[i][j];un=Getfa(now.x);wn=Getfa(now.y);un^wn&&(ans+=now.w,fa[un]=wn);if(tots==n-1) break;
}
}
for(i=cntmax;i>=cntmin;i--) Ans[i]=min(Ans[i],ans-i*mid);
}
int main(){
freopen("1.in","r",stdin);freopen("1.out","w",stdout);
re int i;scanf("%d",&T);while(T--){
scanf("%d%d",&n,&m);Maxn=Gh=0;Me(Ans,0x3f);for(i=1;i<=m;i++) read(S[i].x),read(S[i].y),read(S[i].b),read(S[i].a),Maxn=max(Maxn,S[i].b);
for(i=1;i<=n;i++) fa[i]=i;for(sort(S+1,S+m+1,cmp1),i=1;i<=m;i++){
un=Getfa(S[i].x);wn=Getfa(S[i].y);if(un==wn) continue;fa[un]=wn;G[++Gh]=S[i];
}
for(i=1;i<=n;i++) fa[i]=i;for(sort(S+1,S+m+1,cmp2),i=1;i<=m;i++){
un=Getfa(S[i].x);wn=Getfa(S[i].y);if(un==wn) continue;fa[un]=wn;G[++Gh]=S[i];
}for(i=0;i<=Maxn;i++) GetAns(i);for(i=0;i<n;i++) printf("%d\n",Ans[i]);
}
}
1009
cqy的英语水平真的不错。
因为数据随机所以可以考虑乱搞。
可以发现如果对于一个点的两个状态\((x1,y1)\)和\((x2,y2)\)如果存在二维偏序那么小的那个一定可以不要。
盲猜这样维护单调栈以后不会很多,事实上是对的。时间复杂度\(O(能过)\)
code:
using namespace std;
int T,n,m,A[N+5][N+5],B[N+5][N+5],l,r,Eh,Ch;ll Ans;struct ques{int a,b;}now,E[M+5],C[M+5];vector<ques> F[N+5][N+5];I bool cmp(ques x,ques y){return (x.a==y.a)?x.b<y.b:x.a<y.a;}
I void Solve(){
re int i,j,h;scanf("%d",&n);for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&A[i][j]);for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&B[i][j]),F[i][j].clear();
F[1][1].push_back((ques){A[1][1],B[1][1]});for(i=1;i<=n;i++){
for(j=1;j<=n;j++){if(i==1&&j==1) continue;l=r=Eh=Ch=0;
while(l<F[i][j-1].size()||r<F[i-1][j].size())E[++Eh]=(r>=F[i-1][j].size()||(l<F[i][j-1].size()&&cmp(F[i][j-1][l],F[i-1][j][r])))?F[i][j-1][l++]:F[i-1][j][r++];
for(h=Eh;h;h--) (!Ch||E[h].b>C[Ch].b)&&(C[++Ch]=E[h],0);for(h=Ch;h;h--) C[h].a+=A[i][j],C[h].b+=B[i][j],F[i][j].push_back(C[h]);
}
}for(i=Ans=0;i<F[n][n].size();i++) Ans=max(Ans,1ll*F[n][n][i].a*F[n][n][i].b);printf("%lld\n",Ans);
}
int main(){
freopen("1.in","r",stdin);
scanf("%d",&T);while(T--)Solve();
}
先咕着等NOI完了再补。