牛客2019湘潭大学程序竞赛
都是中文题不需要翻译,话说,我居然sb的在a题看错题目错了3发
ABD签到题,D题贪心思维,没啥好说的
我觉得这题是我卡了最久的题,数学不好推不出来,然后打了个表发现的,官方题解里有证明为什么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 }
一开始感觉没啥思路,想要贪心但是很容易举出反例,但模拟了一下之后有了个类似尺取的思路。因为尺取有个左端点的调整,但我不确定这个左端点该如何调整,所以我改成了枚举左端点,然后去模拟。
具体思路就是,先把字符串分成连续的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 }
没看出是分组背包,一开始时就觉得像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 }
感觉像是一个种类并查集,但并查集的话,我不懂怎么处理让说谎的人最少,所以当时是想到了深搜的方法,但一直觉得时间复杂度很大,就没有写。结束后在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 }
这题的话,没注意到数据范围,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 }