华东交通大学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;
}
View Code

 

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;
}
View Code

 

C 欧涛的生日聚会

题解:

如果整个图没有环的话,显然最多能分的种类数是每个连通分量内最长链的长度之和。

如果整个图是由若干个不相交的环构成的话,最多能分的种类数是所有环长度的最大公约数(找环的时候,可以从连通块内的任意一点开始编号,第二次经过一个点的时候,它第二次的编号减去第一次的编号就是环的大小)。

除了这两种特殊情况之外,还有一种情况是两个环之间可能有公共部分。

我们 DFS 的时候可以倒推:建图的时候不仅连一条uv,边权为 1 的边之外,同时连一条 vu,边权为 −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;
}
View Code

 

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;
}
View Code

 

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");
    }
}
View Code

 

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;
}
View Code

 

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;
    
}
View Code

 

posted @ 2020-11-13 16:03  mcalex  阅读(1253)  评论(0编辑  收藏  举报