Codeforces - 808 补题总结

这周末就要去北京挨打了。。。。。。。。徐州爆零自闭(想想其实是最容易拿银牌的场了主要死磕A结果其他题都没怎么开。。。)

再过一周就要退役了还没上过紫真实自闭Orz

A. Lucky Year

题意就是给一个年份让你求出它离之后第一个x*10^n的形式的年份还有多少年(比如给 10 之后就是 20 答案就是20-10=10)

年份为n,有p位最高位为x  答案就是(x+1)*10^(p-1)-n

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n;
	scanf("%d",&n);

		int cnt=0,lin=n;
		while(n>9){
			n/=10;
			cnt++;
		} 
		n++;
		for (int i=1;i<=cnt;i++)n*=10;
		printf("%d\n",n-lin);
		return 0;
}

B. Average Sleep Time

求和求平均值尺取下随便搞搞

#include<bits/stdc++.h>
using namespace std;
int n,k;
int a[200009];
int main(){
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++)scanf("%d",&a[i]);
	
	long long sum=0,qu=0;
	for (int i=1;i<=k;i++)qu+=a[i];
	sum+=qu;
	for (int i=2;i+k-1<=n;i++){
		qu-=a[i-1];
		qu+=a[i+k-1];
		sum+=qu;
	}
	
	double ans=0.0;
	ans=1.0*sum/(1.0*(n-k+1));
	
	printf("%lf\n",ans);
	
	return 0;
}

C. Tea Party

给n个杯子每个杯子有个容量a[i],现在你有K升饮料要分到每个杯子里去要求满足

1:每个杯子至少半满

2:大杯中的饮料量不能比小杯中的少

3:饮料要分完

贪心下每个杯子至少需要      (a[i]+1)/2【整除意义】   升的饮料,之后剩下的饮料按照大杯到小杯的顺序依次分配就完事儿

#include<bits/stdc++.h>
using namespace std;

struct bei{
	int v,id;
}b[109];

bool cmp(bei a,bei b){
	return a.v>b.v;
}

int main(){
	int n,w,sum=0;
	scanf("%d%d",&n,&w); 
	for(int i=0;i<n;i++) {
	   scanf("%d",&b[i].v);
	   b[i].id=i;
	   sum+=(b[i].v+1)/2;} 
	
	if (sum>w)printf("-1\n");
	else{
		w-=sum;
		//printf("%d\n",w);
		sort(b,b+n,cmp);
		int ans[109];
		for (int i=0;i<n;i++){
			ans[b[i].id]=(b[i].v+1)/2;
			if (w!=0){
				int ll=b[i].v-ans[b[i].id];
				if (ll>=w){
					ans[b[i].id]+=w;w=0;
				}else{
					w-=ll;
					ans[b[i].id]=b[i].v;
				}
				
			}
		}
	   for (int i=0;i<n;i++)printf("%d%c",ans[i],i==n-1?'\n':' ');
	}
	
	return 0;
} 

 

D. Array Division

题意给一个数列a[i]你有一次机会把一个元素调换位置【把它从原来位置取出来然后塞到一个新位置(可以是原来的位置)】

问有没有一种调换方法使得换后的数列能被分成前后两部分且两部分各自的和相同

也是个水题但就是想的写的太烂没法像大佬们一样优雅的A题【疯狂自闭】orz

 

首先读入的时候求一个总和sum,很明显如果总和为奇数是不可能有满足的方案的

和为偶数,那么我们考虑前头的元素后调,

假若我们要调的元素为a,那么就应该存在一个前缀和=sum/2+a 且 a元素在前缀中出现过

那我们就找一遍前缀,计算前缀和并且用set记录出现过的元素,如果满足上述条件那么就OJBK

然后考虑调换有两种(一种后头的元素前调,一种前头的元素后调)

前调就只需要把数列前后翻转然后找后调就等价于前调了

大佬的代码简洁的见者落泪orz

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll sum;
int n;
int a[100009];


bool solve(){
	set<ll>vis;
	ll cnt=0;
	for (int i=0;i<n;i++){
		cnt+=a[i];
		if (cnt>=sum){
			 if (cnt==sum||vis.find(cnt-sum)!=vis.end())
			 return true;
		}
	 vis.insert(a[i]);
	}
	return false;
}

int main(){
	
	scanf("%d",&n);sum=0;
	
	for (int i=0;i<n;i++){
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	//cout<<"*"<<sum<<endl;
	if (sum&1){printf("NO\n");return 0;}
	sum>>=1;
    
	if (solve())printf("YES\n");
	  else{
	  	reverse(a,a+n);
	  	if (solve())printf("YES\n");
	  	  else printf("NO\n");
	  } 
	return 0;
	
}

 

E. Selling Souvenirs

n个物品,背包容量为w,问怎么选物品使得背包中的东西价值最大

题意就是个01背包,但是一看数据范围emmmmmmmmmmm

注意到物品的重量只有3种(1,2,3)那么我们先把物品分类,然后按照价值高的在前排序

之后我们枚举重量为3的物品的数量i,那么剩下的空间就是w-3*i用来放重量为1,2的物品的只要计算出剩下空间能获得最大值就能得到答案

这一段可以用dp算,设一个二维dp数组

dp[i][0]存空间给i的情况下能取的最大价值,dp[i][p]存质量为p的物品取了几个【p={1,2}】

然后就可以很简单的状态转移用dp[i-1],dp[i-2]去更新dp[i]

因为我们是分类后按照物品价值从大到小排序的,重量为p的物品取了x个那么价值就是价值的前x项和【贪心】

枚举重量为3的物品数量,计算能获的最大价值更新答案就OJBK

 

这里计算1,2重量的物品的价值还可以用三分的方法,

因为在给定剩下空间的情况下,假设取的质量为1的个数为x,对应能取到的最大价值就应该为F(x),明显F(x)的图像应该是一个凸的曲线【也可能是单调曲线】,那么非单调函数找最值就可以用3分方法

然后代码写的是DPorz

#include<bits/stdc++.h>
#define ll long long

using namespace std;
int a[100009],b[100009],c[100009];
int n,m,ww,v;
ll dp[300003][3];
 
bool cmp(int a,int b){return a>b;}

int main(){
	scanf("%d%d",&n,&m);
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
	a[0]=0;b[0]=0;c[0]=0;
	for (int i=1;i<=n;i++){
		scanf("%d%d",&ww,&v);
		if (ww==1){a[0]++;a[a[0]]=v;}
	    if (ww==2){b[0]++;b[b[0]]=v;}
	    if (ww==3){c[0]++;c[c[0]]=v;}
	}
	
	sort(a+1,a+1+a[0],cmp);
	sort(b+1,b+1+b[0],cmp);
	sort(c+1,c+1+c[0],cmp);
	
	dp[0][0]=0;dp[0][1]=0;dp[0][2]=0;
	for (int i=1;i<=m;i++){
		ll v1=-1,v2=-1;
		v1=dp[i-1][0]+a[dp[i-1][1]+1];
		if (i>1)v2=dp[i-2][0]+b[dp[i-2][2]+1];
		
		if (v1>v2){
			      dp[i][0]=v1;dp[i][1]=dp[i-1][1];
				  if (dp[i-1][1]+1<=a[0])dp[i][1]++;
				  dp[i][2]=dp[i-1][2];
		}else{
			      dp[i][0]=v2;dp[i][2]=dp[i-2][2];
				  if (dp[i-2][2]+1<=b[0])dp[i][2]++;
				  dp[i][1]=dp[i-2][1];
		}
	}
	
	ll ans=dp[m][0],cnt=0;
	for (int i=1;i<=c[0];i++){
	  cnt+=c[i];
	  if (m-i*3<0)break;
	  ll lin=cnt+dp[m-i*3][0];
	  if (lin>ans)ans=lin;
	}
	
//	for (int i=1;i<=m;i++)printf("*%d  1 %d 2 %d\n",dp[i][0],dp[i][1],dp[i][2]); 
	cout<<ans<<endl;
	
	return 0;
} 

 

F. Card Game

网络流,因为是队友的领域就跳了

 

G. Anthem of Berland

题意给你两个字符串S,T,S中有一些通配符“?”,问怎么填这些通配符使得T在S中出现的次数最多,输出最多的次数

满脑子贪心骚操作但实际是DP题,记下了

DP状态很好想,DP[i]表示前i位中T出现的最大次数

那么我们匹配位置如果匹配到当前的前缀S[i]的后缀为T时,DP[i]=DP[i-strlen(T)]+1;

不然DP[i]=DP[i-1]

但是这仅限于T无法自己重叠的情况orz

当T可以自己重叠时,比如题目给的abcab时,

DP[i]可以从     ] abcab    DP[i-5]        

也可以从         ab ] cab   DP[i-3]转移

也就是说可以从所有的前缀和后缀匹配的位置转移

前缀和后缀匹配。。。。。。。KMP。。。。。

然后这题就没了

我们上来先对T求KMP,

然后开始扫S,如果S的前缀的后缀不为T那么dp[i]=dp[i-1]

不然就跳KMP的next[]数组,用一个cnt[i]数组辅助记录前i位,最后几位刚好是T的情况下T出现的数目的最大值

 

注意这里cnt和dp数组不一样的定义,dp只是前缀i中T出现的次数,而cnt追加了最后的后缀必须是T的约束

而为什么要用cnt呢,这里就是因为通配符的存在

比如T为abcab,我们现在扫到S[i]的后缀为??cab那么肯定转移有两种 ]??cab 和 ??]cab  而我们没法保证第二种i情况下dp[i-3]中对应的填字方法就是abcab,那么这种转移就是无效的,所以我们用cnt[]数组来避免这种情况

详细的就看代码了orz

#include<bits/stdc++.h>
using namespace std;
const int maxn =1e5+7;
int nex[maxn],dp[maxn];
char s[maxn],t[maxn];
int cnt[maxn];

void getnex(char str[],int len){
	int j,i=0;
	nex[0]=j=-1;
	while(i<len){
		while(j!=-1&&str[i]!=str[j])j=nex[j];
		nex[++i]=++j;
	}
}
 
 
int main(){
	cin>>s>>t;
	
	int lent=strlen(t);
	int lens=strlen(s);
	getnex(t,lent);
	
	
	for (int i=lent-1;i<lens;i++){
		//判断后缀是否是t串 
		bool flag=true;
		for (int j=0;j<lent;j++){
			  if(s[i-j]!='?'&&s[i-j]!=t[lent-1-j])
			    { flag=false;
				  break;
				}
		}

		//不是的话dp[i]=dp[i-1]
		//不然看转移因为可能有多种转移过来(多种的重叠)所以跳fail 
		dp[i]=dp[i-1];
		if (flag){
			cnt[i]=dp[i-lent];
			for (int j=nex[lent];j!=-1;j=nex[j]){
				 cnt[i]=max(cnt[i],cnt[i-lent+j]);
			}
			cnt[i]++;
			dp[i]=max(dp[i],cnt[i]);
		}
		
	}
	
	cout<<dp[lens-1]<<endl;
	
	return 0;
}

 

 

天气越来越冷了,心里也是凉凉

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值