湖南省第十五届程序设计竞赛解题报告

mac2024-03-26  31

A 全 1 子矩阵 签到

#include<bits/stdc++.h> using namespace std; const int N=15; int n,m; char s[N][N],t[N][N]; int main() { while(scanf("%d%d",&n,&m)!=EOF) { for(int i=1;i<=n;i++) scanf("%s",s[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) t[i][j]=s[i][j]; for(int i=1;i<=n;i++) for(int j=2;j<=m;j++) if(s[i][j]=='1'&&s[i][j-1]=='1') s[i][j-1]='0'; for(int j=1;j<=m;j++) for(int i=2;i<=n;i++) if(s[i][j]=='1'&&s[i-1][j]=='1') s[i-1][j]='0'; for(int i=1;i<=n;i++) for(int j=m-1;j>=1;j--) if(t[i][j]=='1'&&t[i][j+1]=='1') t[i][j+1]='0'; for(int j=1;j<=m;j++) for(int i=n-1;i>=1;i--) if(t[i][j]=='1'&&t[i+1][j]=='1') t[i+1][j]='0'; int ans=0,res=0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { if(s[i][j]=='1') ans++; if(t[i][j]=='1') res++; } printf(ans==1&&res==1?"Yes\n":"No\n"); } }

B 组合数 使得k=min(k,n-k),那么k就小于等于n/2了,然后暴力去计算组合数,组合数就会只增大不减小,大于1e18直接输出1e18。

#include<bits/stdc++.h> using namespace std; typedef __int128 ll; void print(ll x) { if(x>9) print(x/10); putchar(x%10+'0'); } int main() { ll n,k; long long x,y; while(scanf("%lld%lld",&x,&y)!=EOF) { y=min(y,x-y); n=x;k=y; ll ans=1; for(ll i=1;i<=k;i++) { ans=ans*n/i; n--; if(ans>1e18) break; } if(ans>1e18) ans=1e18; print(ans);putchar('\n'); } }

C Distinct Substrings 对于一个长度为n的数组a1,a2,a3…an,每次只会更新an,an-1 an,an-2 an-1 an,a1… an-1 an,这n个后缀或者新加一个字符

所以最多只会增加n+1个后缀,考虑哪些字符新加某个字符已经出现过了

就是检查每一个在它之前的后缀,看看它之前的后缀是否包含它了

对于第i个后缀和第j个后缀(i<j),如果第i个后缀的前缀等于第j个后缀,说明在第i个后缀的基础上再加一个字符m已经出现过了

这不就是kmp的性质吗,所以我们倒过来求kmp即可,kmp求出的是以i结尾的后缀与字符串的前缀的最大长度。

而对于单个字符的,加个vis数组判断单个字符是否出现过。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+5,mod=1e9+7; int n,top,m,a[N],b[N],nex[N],vis[N]; pair<int,int>s[N]; void kmpinit() { int i=1,k=0; while(i<=n) if(k==0||a[i]==a[k]) nex[++i]=++k; else k=nex[k]; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { top=0; for(int i=1;i<=m;i++) vis[i]=false; for(int i=1;i<=n;i++) nex[i]=0; for(int i=1;i<=n;i++) scanf("%d",&a[i]),vis[a[i]]=true; reverse(a+1,a+1+n); kmpinit(); for(int i=1;i<=n;i++) { int now=nex[i+1]-1; if(now&&i>now) s[++top]={a[i-now],now}; } sort(s+1,s+1+top); top=unique(s+1,s+1+top)-s-1; for(int i=1;i<=top;i++) vis[s[i].first]++; ll ans=0,res=1; for(int i=1;i<=m;i++) { res=res*3%mod; ans^=res*(n+1-vis[i])%mod; } printf("%lld\n",ans); } }

D Modulo Nine 区间乘能取模9等于0,就是要包含两个3的因子,其中0,9包含两个,3,6包含一个,其它的包含0个,对于多个l1,r,l2,r,r相同只需要最小的区间满足了,那么大的区间也同样满足了,暴力O(n^3)dp转移。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=55,mod=1e9+7; int n,m,f[N]; ll dp[N][N][N]; int main() { while(scanf("%d%d",&n,&m)!=EOF) { memset(f,0,sizeof(f)); memset(dp,0,sizeof(dp)); for(int i=1;i<=m;i++) { int l,r; scanf("%d%d",&l,&r); f[r]=max(f[r],l); } dp[0][0][0]=1; for(int i=0;i<n;i++) for(int j=0;j<=i;j++) for(int k=0;k<=j;k++) if(f[i]<=k) { dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k]*6)%mod; dp[i+1][i+1][j]=(dp[i+1][i+1][j]+dp[i][j][k]*2)%mod; dp[i+1][i+1][i+1]=(dp[i+1][i+1][i+1]+dp[i][j][k]*2)%mod; } ll ans=0; for(int j=0;j<=n;j++) for(int k=f[n];k<=j;k++) ans=(ans+dp[n][j][k])%mod; printf("%lld\n",ans); } }

E Numbers 算一算时间复杂度,就可以爆搜了。

#include<bits/stdc++.h> using namespace std; const int N=55; char s[N]; int n,ans; bool vis[N<<1]; void dfs(int pos) { if(pos==n) {ans++;return;} if(s[pos+1]=='0') { if(!vis[0]) { vis[0]=true; dfs(pos+1); vis[0]=false; } } else { int x=s[pos+1]-'0'; if(!vis[x]) { vis[x]=true; dfs(pos+1); vis[x]=false; } x=x*10+s[pos+2]-'0'; if(!vis[x]&&pos+2<=n) { vis[x]=true; dfs(pos+2); vis[x]=false; } } } int main() { while(scanf("%s",s+1)!=EOF) { n=strlen(s+1);ans=0; dfs(0); printf("%d\n",ans); } }

F 4 Buttons

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; int main() { ll n,a,b,c,d; while(scanf("%lld%lld%lld%lld%lld",&n,&a,&b,&c,&d)!=EOF) { swap(b,c); ll ans=n*(n-1)/2%mod*(c+d)%mod*(a+b)%mod+c*n+d*n+1+a*n+b*n; printf("%lld\n",ans%mod); } }

G 字典序 暴力贪心求解,满足条件的区间为不增的区间,初始要找的满足条件的区间是[1,n],枚举列找到第一个满足条件的区间,然后把一系列连续的相等的区间找出来构成新区间,再贪心枚举其它的列。现在赛csu可用的内存大,直接放了O(1)的st表,牛客上只能放个O(log(2000))的线段树,但其实没必要,一个更简单时效更低的方法是每个格子记录它上面第一个不符合条件的位置的距离就可以了。

#include<bits/stdc++.h> using namespace std; const int N=2e3+5; int n,m,a[N][N]; bool t[N][N*3],b[N][N],vis[N]; void build(int l,int r,int k,int now) { if(l==r) { t[now][k]=b[now][l]; return; } int m=l+r>>1; build(l,m,k<<1,now);build(m+1,r,k<<1|1,now); t[now][k]=t[now][k<<1]|t[now][k<<1|1]; } void solve() { for(int j=1;j<=m;j++) { for(int i=2;i<=n;i++) if(a[i-1][j]>a[i][j]) b[j][i]=true; else b[j][i]=false; build(1,n,1,j); } } int query(int l,int r,int k,int x,int y,int now) { if(r<x||l>y) return 0; if(l>=x&&r<=y) return t[now][k]; int m=l+r>>1; return query(l,m,k<<1,x,y,now)|query(m+1,r,k<<1|1,x,y,now); } int query(int j,int l,int r) { assert(l<=r); return query(1,n,1,l,r,j); } int top,tot; pair<int,int>v[N],res[N]; bool judge(int x) { assert(tot<=2000); for(int i=1;i<=tot;i++) if(query(x,v[i].first+1,v[i].second)) return false; top=0; for(int i=1;i<=tot;i++) { int l=v[i].first,r=v[i].second,t=l; for(int j=l+1;j<=r;j++) if(a[j][x]!=a[j-1][x]) { if(j-t>=2) res[++top]={t,j-1};t=j; } if(r-t+1>=2) res[++top]={t,r}; assert(top<=2000); } for(int i=1;i<=top;i++) v[i]=res[i]; tot=top; return true; } int ans[N]; int main() { while(scanf("%d%d",&n,&m)!=EOF) { memset(vis,false,sizeof(vis)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); solve(); tot=0; if(n>1) v[++tot]={1,n}; memset(ans,0,sizeof(ans)); for(int i=1;i<=m;i++) { for(int j=1;j<=m;j++) if(!vis[j]&&judge(j)) { vis[j]=true;ans[i]=j;break; } if(!ans[i]) break; } if(!ans[m]) printf("-1\n"); else for(int i=1;i<=m;i++) printf(i==m?"%d\n":"%d ",ans[i]); } }

H 有向图 线性代数题,附:https://blog.csdn.net/qq_43202683/article/details/100170570#comments

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=550,mod=1e9+7; int n,m; ll inv10000,a[N][N],p[N][N]; ll inv(ll x){return x==1?1:(mod-mod/x)*inv(mod%x)%mod;} void inv_mt(ll a[N][N],int n) { for(int i=1;i<=n;i++) { int p=i; while(p<n&&!a[p][i]) p++; swap(a[p],a[i]); ll t=inv(a[i][i]); for(int j=i;j<=n+n;j++) a[i][j]=a[i][j]*t%mod; for(int k=1;k<=n;k++) { if(k==i) continue; t=a[k][i]; for(int j=i;j<=n+n;j++) a[k][j]=(a[k][j]-t*a[i][j]%mod+mod)%mod; } } } int main() { inv10000=inv(10000); while(scanf("%d%d",&n,&m)!=EOF) { for(int i=1;i<=n;i++) for(int j=1;j<=n+m;j++) scanf("%lld",&p[i][j]),p[i][j]=p[i][j]*inv10000%mod; for(int i=1;i<=n;i++) for(int j=1;j<=n+n;j++) { if(j<=n) a[i][j]=((i==j)-p[i][j]+mod)%mod; else a[i][j]=j-n==i; } inv_mt(a,n); for(int i=1;i<=m;i++) { ll ans=0; for(int j=1;j<=n;j++) { ans=(ans+a[1][n+j]*p[j][n+i]%mod)%mod; } printf(i==m?"%lld\n":"%lld ",ans); } } }

I 2019 树型dp

#include<bits/stdc++.h> using namespace std; const int N=2e4+5,mod=2019; int n,ans,tot,head[N],dp[N][mod],nex[N<<1],to[N<<1],wi[N<<1]; void add(int u,int v,int w){to[++tot]=v;nex[tot]=head[u];head[u]=tot;wi[tot]=w;} void dfs(int u,int p) { memset(dp[u],0,sizeof(dp[u])); for(int i=head[u];i;i=nex[i]) { int v=to[i];if(v==p) continue; dfs(v,u); ans+=dp[u][(mod-wi[i])%mod]; for(int j=0;j<mod;j++) ans+=dp[v][j]*dp[u][(mod-j-wi[i]+mod)%mod]; dp[u][wi[i]]++; for(int j=0;j<mod;j++) dp[u][(j+wi[i])%mod]+=dp[v][j]; } ans+=dp[u][0]; } int main() { while(scanf("%d",&n)!=EOF) { tot=ans=0;for(int i=1;i<=n;i++) head[i]=0; for(int i=1;i<n;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dfs(1,0); printf("%d\n",ans); } }

J Parity of Tuples (Easy) 要解本题必须要注意一句至关重要的话: ^运算是按位与。。。 没看到这句话,那么案例都推不出。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e4+5,mod=1e9+7; int n,m,k,a[10]; ll bit[35],dp[35][1<<10]; ll solve() { for(int i=0;i<=k;i++) for(int j=0;j<1<<m;j++) dp[i][j]=0; for(int i=0;i<m;i++) scanf("%d",&a[i]); dp[0][0]=1; for(int i=0;i<k;i++) { int st=0; for(int j=0;j<m;j++) st|=(a[j]>>i&1)<<j; for(int j=0;j<1<<m;j++) { dp[i+1][j]=(dp[i+1][j]+dp[i][j])%mod; dp[i+1][j^st]=(dp[i+1][j^st]+bit[i]*dp[i][j])%mod; } } return dp[k][(1<<m)-1]; } int main() { bit[0]=3; for(int i=1;i<35;i++) bit[i]=bit[i-1]*bit[i-1]%mod; while(scanf("%d%d%d",&n,&m,&k)!=EOF) { int ans=0; for(int i=1;i<=n;i++) ans=(ans+solve())%mod; printf("%d\n",ans); } }

K 双向链表练习题 翻转链表时,链表之间的边是不变化的,对每个链表给出一个指针l,r记录头和尾,翻转就是交换头和为,如果两个链表相连,将两个链表的头和尾连一条边,记录链表的状态是不是为空,最后一遍dfs过。也可以用平衡树写。

#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,m,l[N],r[N],tot,ep[N],head[N],nex[N<<1],to[N<<1]; void add(int u,int v){to[++tot]=v;nex[tot]=head[u];head[u]=tot;} int top,s[N]; bool vis[N]; void dfs(int u) { s[++top]=u;vis[u]=true; for(int i=head[u];i;i=nex[i]) { int v=to[i]; if(vis[v]) continue; dfs(v); } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { for(int i=1;i<=n;i++) l[i]=r[i]=i,ep[i]=vis[i]=false,head[i]=0; tot=0; for(int i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); if(!ep[a]&&!ep[b]) { add(r[a],l[b]); add(l[b],r[a]); r[a]=r[b]; swap(l[a],r[a]); ep[b]=true; } else if(ep[a]&&!ep[b]) { l[a]=l[b];r[a]=r[b]; swap(l[a],r[a]); ep[a]=false;ep[b]=true; } else if(ep[b]&&!ep[a]) { swap(l[a],r[a]); } } if(ep[1]) printf("0\n"); else { top=0; dfs(l[1]); printf("%d ",top); for(int i=1;i<=top;i++) printf(i==top?"%d\n":"%d ",s[i]); } } }
最新回复(0)