牛客2019湘潭大学程序竞赛

官方题解

  都是中文题不需要翻译,话说,我居然sb的在a题看错题目错了3发

  ABD签到题,D题贪心思维,没啥好说的

CMath Problem

  我觉得这题是我卡了最久的题,数学不好推不出来,然后打了个表发现的,官方题解里有证明为什么a=192*q+1,q是自然数。知道这个之后就很好做了,我们就可以求出在r范围内有多少符合的数,和l-1范围内有多少符合的数做差就可以了。像对于x,我们先求出它范围里有多少个192的倍数,就是q=x/192,然后a是192*q+1,如果x刚好是q的倍数,那么q就去掉一个1个,然后是求∑a,而范围内192的倍数有q个,∑a就是192*(1+2+3+..+q)+q+1

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 typedef long long ll;
 5 ll solve(int x)
 6 {
 7     if(!x)
 8         return 0;
 9     ll q=x/192;
10     if(x%192==0)
11         q--;
12     return 192ll*q*(1+q)/2+q+1;//+1是对1这个情况特判 
13 }
14 int main()
15 {
16     int t,l,r;
17     scanf("%d",&t);
18     while(t--)
19     {
20         scanf("%d%d",&l,&r);
21         printf("%lld\n",solve(r)-solve(l-1));
22     }
23     return 0;
24 }
来一份192的题

FBlack & White

  一开始感觉没啥思路,想要贪心但是很容易举出反例,但模拟了一下之后有了个类似尺取的思路。因为尺取有个左端点的调整,但我不确定这个左端点该如何调整,所以我改成了枚举左端点,然后去模拟。

  具体思路就是,先把字符串分成连续的01子串,比如0001110011就可以分成4个字串,然后相应的权值就是它们的长度,v[0]=3,v[1]=3,v[2]=2,v[3]=2,这样的话,每个子串的下一个子串就是和它不同的需要翻转,而下下个就是和它相同的,不需要翻转。然后就枚举一个子串v[i]作为头部,然后就模拟看最多翻转m个字符,它可以延伸的长度,详情见代码。

 1 #include<cstdio>
 2 #include<vector>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=100118;
 6 char s[N];
 7 vector<int> v;
 8 int main()
 9 {
10     int t,n,m;
11     scanf("%d",&t);
12     while(t--)
13     {
14         scanf("%d%d",&n,&m);
15         scanf("%s",s);
16         v.clear();
17         int sum=0,ans=0,len;
18         for(int i=0;s[i]!='\0';i++)
19         {
20             sum++;
21             if(s[i]!=s[i+1])
22             {
23                 v.push_back(sum);
24                 ans=max(ans,sum);
25                 sum=0;
26             }
27         }
28         for(int i=0,j,mm;i<v.size();i++)
29         {
30             len=v[i];//枚举第i个子串作为头部 
31             j=i+1; 
32             mm=m;
33             while(j<v.size()&&mm>=v[j])//如果存在下个子串,并且翻转次数可以把它全翻转过来 
34             {
35                 len+=v[j];//全翻转过来,长度拼接上 
36                 mm-=v[j];//翻转次数减少 
37                 if(j+1<v.size())//如果存在下下个子串 
38                     len+=v[j+1];//长度直接拼接上 
39                 j+=2;//下一个要翻转的位置 
40             }
41             if(j<v.size())//如果还有下一个子串 
42                 len+=mm;//那么说明剩余的翻转次数不足以把v[j]全部翻转
43                 //但可以翻转v[j]的前mm个使得len更长 
44             ans=max(ans,len);
45         }
46         printf("%d\n",ans);
47     }
48     return 0;
49 }
翻翻转转都转不清

Hchat

  没看出是分组背包,一开始时就觉得像dp,但感觉三个循环可能会超时,没敢写,后来没其他想法了,还是试了一下。首先定义的dp[i][j]就是到第i天时,生气值为j的最少在线时间,那么如果我们知道第i天的生气值为j的最少在线时间b[i][j],那么转移过程就很好转移了,就是dp[i][j+k]=min(dp[i][j],dp[i-1][j]+b[i][k]),意思就是枚举到第i-1天生气值为j的情况,以及第i天生气值为k的情况,那么到第i天生气值为j+k的情况就相应更新。

  而b[i][j],怎么处理出来呢,我们先处理个a[i][j]为,第i天前j个小时的生气值的前缀和,然后对于每一天i,我们枚举个上线时间j和下线时间k,这样在线时间就是k-j+1,而在这段时间上线会造成的生气值就是a[i][m]-(a[i][k]-a[i][j-1]),相应地更新就好,详情见代码。

 1 #include<cstdio>
 2 #include<vector>
 3 #include<algorithm>
 4 using namespace std;
 5 const int inf=0x3f3f3f3f;
 6 int dp[250][250],a[250][250],b[250][250];
 7 char s[250];
 8 int main()
 9 {
10     int t,n,m,K;
11     scanf("%d",&t);
12     while(t--)
13     {
14         scanf("%d%d%d",&n,&m,&K);
15         for(int i=0;i<=n;i++)
16             for(int j=0;j<=K;j++)//前面值初始化m,wrong了三发 
17                 dp[i][j]=b[i][j]=inf;
18         for(int i=1;i<=n;i++)
19         {
20             scanf("%s",s+1);
21             for(int j=1;j<=m;j++)//处理前缀和 
22                 a[i][j]=a[i][j-1]+s[j]-'0';
23         }
24         int sq;
25         for(int i=1;i<=n;i++)
26         {
27             sq=a[i][m];//当天最多累计的生气值是sq
28             for(int j=sq;j<=K;j++)
29                 b[i][j]=0;//那么对于sq及之后的生气值最少上线时间都是0 
30             for(int j=1;j<=m;j++)//枚举上线时间 
31                 for(int k=j;k<=m;k++)//枚举下线时间 
32                 {
33                     sq=a[i][m]-(a[i][k]-a[i][j-1]);//如果在这个时间段下线,当天累计的生气值 
34                     b[i][sq]=min(b[i][sq],k-j+1);
35                 }
36         }
37         dp[0][0]=0;
38         for(int i=1;i<=n;i++)
39             for(int j=0;j<=K;j++)//枚举前i-1天,生气值为j的情况 
40                 for(int k=0;j+k<=K;k++)//枚举第i天,生气值为k的情况 
41                     dp[i][j+k]=min(dp[i][j+k],dp[i-1][j]+b[i][k]); //更新前i天,生气值为j+k的情况 
42         int ans=inf;
43         for(int i=0;i<=K;i++)
44             ans=min(ans,dp[n][i]);
45         printf("%d\n",ans);
46     }
47     return 0;
48 }
爱的魔力转圈圈

GTruthman or Fakeman

  感觉像是一个种类并查集,但并查集的话,我不懂怎么处理让说谎的人最少,所以当时是想到了深搜的方法,但一直觉得时间复杂度很大,就没有写。结束后在qdcxk的点拨下,仔细想想时间复杂度大概就O(5*m),并不是我想的n方。

  首先我们把关系视为边来建图,这样就可以得到好几个连通块,而在一个个连通块中,要是有一个人的身份确定了,那么这个连通块中的所有人的身份都确定了,因为一个人不是诚实的就是说谎的。所以我们就可以在每个连通块随便挑一个人,分别让他是0(说谎的),和1(诚实的),然后记录两个情况下连通块中说谎的人数量,然后挑少的那个情况来对这个连通块的所有人确定身份,这样的话对每个块挑的那个人搜一遍0的情况,然后恢复回未知身份,再搜一遍1的情况,然后恢复回未知身份,最后确定身份,一个连通块最多就进行了5次深搜,详情见代码。

  1 #include<cstdio>
  2 const int N=101108,M=101108;
  3 struct Side{
  4     int v,ne,w;
  5 }s[M<<1];
  6 int n,m,sn,head[N],sf[N];
  7 void init()
  8 {
  9     sn=0;
 10     for(int i=0;i<=n;i++)
 11         sf[i]=head[i]=-1; //-1代表还未明确身份 0代表欺骗者,1代表老实人 
 12 }
 13 void add(int u,int v,int w)
 14 {
 15     s[sn].v=v;
 16     s[sn].w=w; 
 17     s[sn].ne=head[u];
 18     head[u]=sn++;
 19 }
 20 void dfs(int u,int w,int &num)
 21 {
 22     if(num==-1)
 23         return ;
 24     if(w==0)
 25         num++;
 26     sf[u]=w;
 27     for(int i=head[u];~i;i=s[i].ne)
 28     {
 29         int v=s[i].v;
 30         if(sf[v]==-1)//只有u是1且u说v是1时以及u是0且u说v是0时,v才是1 
 31             dfs(v,((w^s[i].w)^1),num);
 32         else if(sf[v]!=((w^s[i].w)^1))
 33         {//如果身份以已经明确的身份冲突,那么这种情况就不可能 
 34             num=-1;
 35             return ;
 36         }
 37     }
 38 }
 39 void clear(int u)//清空身份 
 40 {
 41     sf[u]=-1;
 42     for(int i=head[u];~i;i=s[i].ne)
 43     {
 44         int v=s[i].v;
 45         if(sf[v]!=-1)
 46             clear(v);
 47     }
 48 } 
 49 int main()
 50 {
 51     int t,u,v,w;
 52     scanf("%d",&t);
 53     while(t--)
 54     {
 55         scanf("%d%d",&n,&m);
 56         init();
 57         while(m--)
 58         {
 59             scanf("%d%d%d",&u,&v,&w);
 60             add(u,v,w);
 61             add(v,u,w);//这里要建双向边,否则u和v可能会在不同的连通块 
 62 //            由  u v w
 63 //                1 1 1
 64 //                1 0 0
 65 //                0 1 0
 66 //                0 0 1
 67 //            u说v是w,那么v就应该说u是w    
 68         }
 69         int num0,num1,is=1;
 70         for(int i=1;i<=n;i++)
 71             if(sf[i]==-1)
 72             {
 73                 num0=num1=0;
 74                 //分别假设一遍i是0或1的情况,并记录下相应的欺骗者的个数
 75                 dfs(i,0,num0); 
 76                 clear(i);//因为还没明确是什么身份,清空回未知状态 
 77                 dfs(i,1,num1);
 78                 clear(i);
 79                 //看那种情况下的欺骗者的个数比较少,来确定相应的身份 
 80                 if(num0!=-1&&num1!=-1)
 81                 {
 82                     if(num0<=num1)
 83                         dfs(i,0,num0);
 84                     else
 85                         dfs(i,1,num1);
 86                 }
 87                 else if(num0!=-1)//只可能是i是0的情况 
 88                     dfs(i,0,num0);
 89                 else if(num1!=-1)//只可能是i是1的情况 
 90                     dfs(i,1,num1);
 91                 else//都不可能,也就是有矛盾的产生 
 92                 {
 93                     is=0;
 94                     break;
 95                 }
 96             }
 97         if(is)
 98         {
 99             for(int i=1;i<=n;i++)
100                 printf("%d",sf[i]);
101         }
102         else
103             printf("-1");
104         printf("\n");
105     }
106     return 0;
107 }
真真假假

 EWatermelon

  这题的话,没注意到数据范围,m最大才1e6,完全可以模拟吃西瓜的过程,不过当时我想到的就是一个二分的方法,可惜没写,啊,看来有想法就果断去写。

  首先,二分个什么呢,二分吃了多少轮西瓜,因为lililalala每次都是吃a[p]个西瓜(p是lililalala所在的下标),而其他人最少吃1个,最多吃a[i[个,那么第k轮的时候到lililalala,西瓜最多剩下maxm=m-(k*(n-1+a[p])+p-1)个,最少剩下minm=m-(k*sum[n]+sum[p-1]) ,(sum是前缀和)那么如果minm>0,就说明西瓜没吃完,这第k轮并不能让到lililalala吃时没西瓜吃,k得增大,而如果maxm<0就说明这第k轮还没到lililalala吃西瓜就吃完了,k得减小,而minm<=0&&maxm>=0就说明到lililalala吃之前西瓜是够吃的,而且可以控制到lililalala吃的时候,西瓜刚好没有了。

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 typedef long long ll;
 5 const int N=100118;
 6 int n,p,a[N]={0};
 7 ll m,sum[N]={0};
 8 int check(ll k)
 9 {
10     ll maxm=m-(k*(n-1+a[p])+p-1);
11     ll minm=m-(k*sum[n]+sum[p-1]);
12     if(minm>0)//没吃完 
13         return 1;
14     else if(maxm<0)//不够吃 
15         return -1;
16     else
17         return 0;
18 }
19 int main()
20 {
21     int t;
22     scanf("%d",&t);
23     while(t--)
24     {
25         scanf("%d%lld",&n,&m);
26         p=0;
27         for(int i=1;i<=n;i++)
28         {
29             scanf("%lld",&a[i]);
30             if(a[i]>a[p])
31                 p=i;
32             sum[i]=sum[i-1]+a[i];
33         }
34         if(n==1)
35         {
36             printf("YES\n");
37             continue;
38         }
39         int flag=1;
40         ll l=0,r=(m-p+1)/(n-1+a[p]);
41         while(l<=r)
42         {
43             ll mid=(l+r)>>1;
44             flag=check(mid);
45             if(flag==1)
46                 l=mid+1;
47             else if(flag==-1)
48                 r=mid-1;
49             else
50                 break;
51         }
52         if(!flag)
53             printf("YES\n");
54         else
55             printf("NO\n");
56     }
57     return 0;
58 }
我也想吃西瓜
posted @ 2019-05-05 21:23  新之守护者  阅读(203)  评论(0编辑  收藏  举报