华东交通大学2020年ACM“双基”程序设计竞赛 题解
A 签到
题解:直接输出即可,注意行末换行。
#include <iostream> #include <fstream> using namespace std; int main(){ int n,m; freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取 freopen("out.txt","w",stdout); //输出重定向,输出数据将保存out.txt文件中 cout<<"祝贺祖国成立71周年!"<<endl; return 0; }
B 不要666升级版
题解:
本题可以用数位DP来解:
首先定义状态dp[len][sum1][sum2] 表示长度为len对6取模为sum1,各位上的数字和为sum2。
然后把一个数拆成a + b的形式,如878327 为 800000 + 78327。
由(a + b)^3 = a ^3 + b ^ 3 + 3 * a^2 * b + 3 * a * b ^ 2。
那么所有满足条件的数的立方和,其中cnt为这些数字的个数:
a ^ 3 * cnt + (b ^ 3 + c ^ 3 + …) + 3 * a ^ 2 * (b + c + …) + 3 * a * (b ^ 2 + c ^ 2 +….)
我们发现:(b^3 + c ^ 3 + ….)为立方和,(b + c + …. )为和,(b ^2 + c ^ 2 + …)为平方和,所以我们需要维护4个值:这些数字的个数,和,平方和,立方和就可以解决这题了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int b[25]; ll bit[25]; const ll mod = 1e9 + 7; struct st{ ll num; ll sum; ll sqsum; ll cusum; st(ll _num,ll _sum,ll _sqsum,ll _cusum):num(_num),sum(_sum),sqsum(_sqsum),cusum(_cusum){ } st():num(-1),sum(0),sqsum(0),cusum(0){ } }dp[25][10][180]; st dfs(int pos, int num1, int num2, bool state){ if(pos ==- 1){ if(num1 == 0 || num2 % 6 == 0){ return st(0,0,0,0); } return st(1, 0, 0, 0); } if(!state && dp[pos][num1][num2].num != -1){ return dp[pos][num1][num2]; } int up = state ? b[pos] : 9; st ans; ans.num = 0; for(int i = 0;i <= up; i++){ if(i == 6){ continue; } st tem = dfs(pos - 1, (num1 * 10 + i) % 6, num2 + i, state && i == up); ans.num = (ans.num + tem.num) % mod; ans.sum = ( ans.sum + i * bit[pos] % mod * tem.num % mod + tem.sum) % mod; ans.sqsum = (ans.sqsum + tem.sqsum) % mod; ll temp = i * bit[pos] % mod; ans.sqsum = (ans.sqsum + temp * temp % mod * tem.num % mod) % mod; ans.sqsum = (ans.sqsum + 1ll * 2 * temp % mod * tem.sum % mod) % mod; ans.cusum = (ans.cusum + tem.cusum) % mod; ans.cusum = (ans.cusum + temp * temp % mod * temp % mod * tem.num % mod) % mod; ans.cusum = (ans.cusum + 1ll * 3 * temp % mod * temp % mod * tem.sum % mod) % mod; ans.cusum = (ans.cusum + 1ll * 3 * temp * tem.sqsum % mod) % mod; } if(!state){ dp[pos][num1][num2] = ans; } return ans; } ll solve(ll m){ int len = 0; while(m){ b[len++] = m % 10; m /= 10; } return dfs(len-1, 0, 0, true).cusum; } int main(){ bit[0] = 1; for(int i = 1; i <= 20; i++){ bit[i] = bit[i-1] * 10%mod; } ll n, m; // srand((unsigned int)time(NULL)); // freopen("test3.out","w",stdout); // freopen("test3.in","r",stdin); // int t = 5000; // while(t--){ // n = ((double)rand() / RAND_MAX) * 1000000000000000000; // m = ((double)rand() / RAND_MAX) * 1000000000000000000; // if(n > m){ // n = n ^ m; // m = n ^ m; // n = n ^ m; // } // if(n < 0 || m < 0) continue; // printf("%lld %lld\n", n, m); // } // while(scanf("%lld%lld",&n, &m)!=EOF){ printf("%lld\n",(solve(m) - solve(n - 1) + mod) % mod); } return 0; }
C 欧涛的生日聚会
题解:
如果整个图没有环的话,显然最多能分的种类数是每个连通分量内最长链的长度之和。
如果整个图是由若干个不相交的环构成的话,最多能分的种类数是所有环长度的最大公约数(找环的时候,可以从连通块内的任意一点开始编号,第二次经过一个点的时候,它第二次的编号减去第一次的编号就是环的大小)。
除了这两种特殊情况之外,还有一种情况是两个环之间可能有公共部分。
我们 DFS 的时候可以倒推:建图的时候不仅连一条u→v,边权为 1 的边之外,同时连一条 v→u,边权为 −1 的边(这种连边方式可以确保我们从任意一个点开始,都可以遍历整个连通块)。
对于每个连通块,我们还是任意选一个点开始编号,经过一条边的时候将编号加上边权,和上面一样,第二次经过同一个点的时候,它第二次的编号减去第一次的编号就是环的大小。
这样我们就可以找出所有的环了。
(最后别忘了题目要求面具最少要有三种)
#include <cstdio> #include <cstring> #include <algorithm> #include <set> #define INF 1e9 using namespace std; struct edge { int v,w; bool operator<(const edge&a)const { return v<a.v||(v==a.v&&w<a.w); } }; set<edge> e[100005]; int dis[100005],vis[100005],ans,res,cnt,maxv,minv; int gcd(int x,int y) { return y==0?x:gcd(y,x%y); } void dfs(int u,int d) { if(dis[u]) { ans=gcd(ans,abs(d-dis[u])); return; } dis[u]=d,vis[u]=1; maxv=max(maxv,dis[u]); minv=min(minv,dis[u]); for(auto i:e[u]) dfs(i.v,d+i.w); } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); e[u].insert({v,1}); e[v].insert({u,-1}); } for(int i=1;i<=n;i++) if(!vis[i]) { maxv=-INF,minv=INF; dfs(i,1); res+=maxv-minv+1; } if(ans) { if(ans<3)puts("-1 -1"); else for(int i=3;i<=ans;i++) if(ans%i==0) { printf("%d %d\n",ans,i); break; } } else { if(res<3)puts("-1 -1"); else printf("%d 3\n",res); } return 0; }
D 欧涛玩游戏
题解:
直接判断输入的时候可以构成直角三角形即可,注意多组输入、不要用int 否则是答案错误
#include <iostream> #include <fstream> using namespace std; int main(){ int a,b,c; // ifstream infile; //输入流 // ofstream outfile; //输出流 // // infile.open("C:\\Users\\z1164\\Desktop\\校赛数据\\玩游戏.in", ios::in); while (cin>>a>>b>>c) // 若未到文件结束一直循环 { // infile >> a >> b >> c; //cout<<a<<" "<<b<<" "<<c<<endl; if ((a*a==b*b+c*c)||(b*b==a*a+c*c)||(c*c==a*a+b*b)) cout<<"YES"<<endl; else cout<<"NO"<<endl; } //cin>>a>>b>>c; return 0; }
E 生而为人、我很抱歉
题解:
题目描述可能有问题、只需要考虑输入的字符串彼此之间是否符合字典序,不用考虑单个字符串是否是按字典序排序。
#include<bits/stdc++.h> using namespace std; int main(){ int n; int m; while(cin>>n>>m){ string s1[101]; string s2[105]; int flag=1; for(int i=1;i<=n;i++){ cin>>s1[i]; s2[i]=s1[i]; } sort(s2+1,s2+1+n); for(int i=1;i<=n;i++){ if(s2[i]!=s1[i]){ flag=0; break; } } if(flag)puts("YES"); else puts("NO"); } }
F 糖果店
题解:
可以看出 Bob可以购买的是 这个数组包含最大值和最小值的子串个个数,因此我们从左到右遍历这个序列,用两个指针t1,t2 来表示到目前为止,最大值和最小值位置的最大值,则当前位置对答案的贡献就是
min( t1 + t2 )。
Jerry就容易很多,统计出最大值和最小值的数量,则最终的答案就是从最大值构成的集合中选取非0个个数的方式*从最小值构成的集合中选取非0个个数的方式*从除去最大值和最小值构成的集合中选取任意个数的方式。
ps:c(n,0) + c(n,1) + c(n,2) + .....+ c(n,n) = 2n.
题意中忘了写模数,表示非常抱歉
#include<iostream> #include<cstring> #include<algorithm> using namespace std; #define ll long long const ll inf=0xffffff; const ll mod=1000000007; ll n,m,k,t; ll num[100010]; ll cal(ll a,ll b) { ll ans=1; while(b) { if(b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } int main() { cin>>t; while(t--) { cin>>n; ll min1=inf,max1=0,min_num=0,max_num=0; ll sum1=0,sum2=0; for(int i=1;i<=n;i++) { cin>>num[i]; min1=min(min1,num[i]); max1=max(max1,num[i]); } if(min1==max1) { sum1=n*(n+1)/2%mod; sum2=cal(2,n)-1; cout<<sum1<<' '<<sum2<<endl; continue; } ll t1=0,t2=0; for(int i=1;i<=n;i++) { if(min1==num[i]) { t1=i; min_num++; } if(max1==num[i]) { t2=i; max_num++; } sum1=(min(t1,t2)+sum1)%mod; } sum2=((cal(2,min_num)-1)%mod*(cal(2,max_num)-1)%mod*cal(2,n-max_num-min_num))%mod;//排列组合 /*sum2=(cal(2,n)-cal(2,n-max_num)-cal(2,n-min_num)+cal(2,n-max_num-min_num))%mod;//容斥原理 if(sum2<0) sum2+=mod;*/ cout<<sum1<<' '<<sum2<<endl; } return 0; }
G 钥匙
题解:
设lock[i]表示:有 i个槽的钥匙的个数。
设one[i]表示:有 i个槽且左边第一个槽深度为1 的钥匙的个数。
设two[i]表示:有 i个槽且左边第一个槽深度为2 的钥匙的个数。
..
..
设six[i]表示:有 i个槽且左边第一个槽深度为6的钥匙的个数。
则显然:lock[i]=one[i]+two[i]+ three[i]+four[i]+five[i]+six[i]
由于易知:one[i]=six[i],two[i]=three[i]=four[i]=five[i]
则可以得到:lock[i]= one[i]*2+two[i]*4
其中:
one[i]=one[i-1]+two[i-1]+three[i-1]+four[i-1]+five[i-1]+a[i];
=one[i-1]+two[i-1]*4 +a[i]
two[i]=one[i-1]*2+two[i-1]*4 + b[i]
one[i]=one[i-1]+two[i-1]+three[i-1]+four[i-1]+five[i-1]+a[i]=one[i-1]+two[i-1]*4 +a[i]
two[i]=one[i-1]*2+two[i-1]*4 +b[i]
其中,a[i] 和b[i]的含义分别是:
a[i]表示“有 i个槽且左边第一个槽深度为1,同时这种钥匙在除掉第一个槽后不再是钥匙”的钥匙的个数。
例如 123,124,125,134,135,145,126,136,146,156,1233
b[i]表示“有 i个槽且左边第一个槽深度为2,同时这种钥匙在除掉第一个槽后不再是钥匙” 的钥匙的个数。
关键的是求a[i]和b[i],我们可以得到如下表达式:
a[i]=(2^(i-1)-2)*6+(2^(i-2)-1)*4
b[i]=(2^(i-1)-2)*9
a[i] 的各部分的含义如下:
(2^(i-1)-2)*6:
当去掉第一位,后面i-1位只有 (2,3)或者 (2,4) 或者(2,5) 或者(3,4) 或者(3,5) 或者(4,5)两种数字的时候,显然是不合法的钥匙(不满足至少有3个不同的深度),加上第一位的1则显然是一个合格的钥匙。所以后面的数字可以为一个组合中两个数字的任意一个,根据乘法原理i-1位一共有2^(i-1)种组合,当然还需要去掉两种特殊情况,就是后面i-1位完全相同的情况。满足这种条件的一共有上面六种组合,所以得到(2^(i-1)-2)*6。
(2^(i-2)-1)*4:
当去掉第一位,后面i-1位只有 (2,6)或者 (3,6) 或者(4,6) 或者(5,6)两种数字的时候,显然是不合法的钥匙(不满足至少有3个不同的深度),加上第一位的1则“可能”是一个合格的钥匙。因为要注意满足“相连的槽其深度之差不得为5”这个条件,所以,紧跟着1的绝对不能是6,这样,相对于上面的分析,后面i-2位可以是每组中的任意一个,所以有2^(i-2),还要减去1是因为同样要排除后面全部是和第2位一样的数字这样的情况。满足这种条件的一共有上面的四种组合,所以得到(2^(i-2)-1)*4。
b[i] 的含义如下:
(2^(i-1)-2)*9:
当去掉第一位,后面i-1位只有 (1,3)或者 (1,4) 或者(1,5) 或者(3,4) 或者(3,5) 或者(3,6) 或者(4,5) 或者(4,6) 或者(5,6) 两种数字的时候,显然是不合法的钥匙(不满足至少有3个不同的深度),加上第一位的1则显然是一个合格的钥匙。所以后面的数字可以为一个组合中两个数字的任意一个,根据乘法原理i-1位一共有2^(i-1)种组合,当然还需要去掉两种特殊情况,就是后面i-1位完全相同的情况。满足这种条件的一共有上面9种组合,所以得到(2^(i-1)-2)*9。
综上我们就可以推出
lock[i]=one[i]*2+two[i]*4
one[i]=six[i]
two[i]=three[i]=four[i]=five[i]
one[i] = one[i-1]+two[i-1]*4 +a[i]
two[i] = one[i-1]*2+two[i-1]*4 +b[i]
a[i]=(2^(i-1)-2)*6+(2^(i-2)-1)*4
b[i]=(2^(i-1)-2)*9
#include<cstdio> #include<algorithm> #define ll long long using namespace std; ll s[3][30]; ll ans[30]; ll pow_1(int a){ int r=1; for(int i=1;i<=a;i++) r*=2; return r; } int main(){ int n; s[1][2]=0; s[2][2]=0; ans[1]=0; ans[2]=0; for(int i=3;i<=25;i++){ s[1][i]=s[1][i-1]+4*s[2][i-1]+6*(pow_1(i-1)-2)+4*(pow_1(i-2)-1); s[2][i]=2*s[1][i-1]+4*s[2][i-1]+9*(pow_1(i-1)-2); ans[i]=2*s[1][i]+4*s[2][i]; } while(scanf("%d",&n)!=EOF){ printf("%lld\n",ans[n]); } return 0; }