CF Round #540 (Div. 3)

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:https://codeforces.com/contest/1118

B
给定一个数列,问有多少个点,断掉这个点后的新数列,奇数下标部分和等于偶数下标部分的和
----------------------------------------------------------------------------
奇偶部分的前缀和分开搞,去掉a[i]后:
ji[i-1]+(ou[n]-ou[i])==ou[i-1]+(ji[n]-ji[i]) 说明第i个是可以去掉的
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[200005],ji[200005],ou[200005],ans;
int main(){
	scanf("%d",&n);
	fo(i,1,n){
		scanf("%d",&a[i]);
	}
	ji[1]=a[1];
	fo(i,2,n){
		if(i&1) ji[i]=ji[i-1]+a[i],ou[i]=ou[i-1];
		else ji[i] = ji[i-1],ou[i]=ou[i-1]+a[i];
	}
	fo(i,1,n){	
	//	cout<<"ji:"<<ji[i-1]<<" "<<(ou[n]-ou[i])<<endl;
	//	cout<<"ou:"<<ou[i-1]<<" "<<(ji[n]-ji[i])<<endl;
		if(ji[i-1]+(ou[n]-ou[i])==ou[i-1]+(ji[n]-ji[i]))ans++;
	}
	cout<<ans;
	return 0;
}
C
给定n*n个数,排序这n*n个数带n*n棋盘,使得棋盘左右折叠和上下折叠一样
--------------------------------------------------------------
a[i][j]在棋盘上对称的位置为:
a[i][n-j+1], a[n-i+1][j] , a[n-i+1][n-j+1]
这4个位置必须一样
当然可能存在4个位置是同一个位置,或者4个位置实际只有两个,或者有4个
那么我么首先计算前(n+1)/2行,(n+1)/2列,的位置是哪种情况
然后枚举这部分数,把数字填上去
填充顺序必须是:4,2,1,否则先枚举小的会大大的那些数给拆散
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=1000;
int n,cnt[1005],ans[25][25],tot;
vector<pair<int, pair<int,int> > >ver;
int main(){
	cin>>n;
	int x;
	fo(i,1,n*n){
		scanf("%d",&x);
		cnt[x]++;
	} 
	// 所有的对称中有1,2,4这3种对称 
	fo(i,1,(n+1)/2){
		fo(j,1,(n+1)/2){
			if(i!=(n-i+1) && j!=(n-j+1))ver.push_back({-4,{i,j}});
			else if(i!=(n-i+1) || j!=(n-j+1))ver.push_back({-2,{i,j}});
			else ver.push_back({-1,{i,j}});
		}
	}
	sort(ver.begin(), ver.end()); 
	// 必须从大到小枚举,以防拆了某些大的数 
	for(auto p : ver){
		int num = -p.first;
		int i = p.second.first;
		int j = p.second.second;
		int idx = 1;
		while(idx<=N && cnt[idx]<num){
			idx++;	
		}
		if(idx>N){ // 无解 
			puts("NO");
			return 0;
		}
		ans[i][j] = ans[i][n-j+1] = ans[n-i+1][j] = ans[n-i+1][n-j+1] = idx;
		cnt[idx] -= num;
	}
	puts("YES");
	fo(i,1,n){
		fo(j,1,n){
			printf("%d ", ans[i][j]);
		}
		putchar(10);
	}
	return 0;
} 
D1&D2
给定n杯咖啡,任务页数m
每杯咖啡含有a[i]咖啡因
一天喝第i杯咖啡,能做max(0, a[i]-i+1)页
不喝做不了,求最快多少天完成 
---------------------------------------
D1
dp[i][j]表示到第i天,喝了前j杯咖啡,
d(k+1,j)表示一天喝第k+1到j杯咖啡的工作页数(计算有误,但不影响)
for(int k=i-1; k<j; k++)
		dp[i][j] = max(dp[i][j], dp[i-1][k] + d(k+1,j));
---------------------------------------
D2
由于一天喝多杯咖啡是会扣咖啡因的
所以为了尽量避免扣掉更多的咖啡因,
我么先喝咖啡因多的,并且尽量一天喝少点,均摊到每天
比如 喝 3 天,7杯咖啡
1     2     3
a[1]  a[2]  a[3]
a[4]  a[5]  a[6]
a[7]
如上均摊喝法,使得咖啡因多的咖啡尽量在最开始被喝
然后我们二分一下天数即可 

D1

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N = 200005;
int n,m,a[N];
int dp[10005][105],sum[105];
int d(int i, int j){
	int ret = sum[j] - sum[i-1];
	ret -= (1+(j-i))*(j-i)/2; // 这里计算有误没按照max(0,a[i]-t+1)来 
	return ret;				  // 但是枚举了所有情况所以不怕...
							  // 一杯咖啡杯减到小于0,还不如再下次喝(枚举了) 
}
int main(){
	cin>>n>>m;
	fo(i,1,n)scanf("%d",&a[i]);
	sort(a+1, a+1+n);
	reverse(a+1, a+1+n);
	fo(i,1,n)sum[i]=sum[i-1]+a[i];
	int ans = -1;
	for(int i=1; i<=m; i++){ // 天 
		for(int j=0; j<=n; j++){
			for(int k=i-1; k<j; k++){
				dp[i][j] = max(dp[i][j], dp[i-1][k] + d(k+1,j));
				if(dp[i][j]>=m){
					ans = i;
					cout<<i;
					return 0; 
				}
			}
		}
	}
	cout<<ans;
	return 0;
}

D2

#include<bits/stdc++.h> 
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,m,a[200005];
bool ok(int mid){
	int cnt = 0,ans=0;
	for(int i=n; i>=1;){
		for(int j=1; j<=mid; j++){// 天 
			ans += max(0, a[i]-cnt);
			if(ans>=m)return 1;
			i--;
			if(i<1)return 0;
		}
		cnt++;
	}
	return 0;
}
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	int l=1,r=n,ans=-1;
	while(l<=r){
		int mid = (l+r)>>1;
		if(ok(mid)){
			r = mid-1;
			ans = mid;
		}else l = mid+1;
	}
	cout<<ans;
	return 0;
}
E
问是否存在n对(a,b),1<=a,b,<=k
满足:
每对不同
第i+1对和第i对,a[i+1] != a[i] && b[i+1] != b[i]
a[i] != b[i]
-------------------------------------------------
容易知道一共有 k(k-1) 对
若k(k-1)>=n
我们可以这么构造
a:1,2,3,4..k,1,2,3,...k,1,2,... // k-1趟 
b:2,3,...k,1,3,4,...k,1,2,4,5,...// k-1趟,第k趟就重复了
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
ll n,k;
int main(){
	cin>>n>>k;
	if(k*(k-1)<n){
		puts("NO"); 
	}else{
		puts("YES");
		// b:1,2,3,4..k,1,2,3,...k,1,2,... // k-1趟 
		// g:2,3,...k,1,3,4,...k,1,2,4,5,...// k-1趟,第k趟就重复了
		int cnt=0;
		fo(i,0,n-1){
			if(i%k==0)cnt++;
			int x = i%k + 1;
			int y = (i+cnt)%k+1;
			cout<<x<<" "<<y<<endl;
		} 
	}
	return 0;
} 
F1
给定一颗树,树上的节点由0,1,2染色
问存在多少条边,使得切开这条边后,1归为一类,2归为一类
-----------------------------------------------------
我们可以dfs一下
dfs(x)表示:x及其子孙后代的1的数量和2的数量
当dfs(x)的1的数量等于总数1的数量,2的数量为0
或者      2的数量等于总数2的数量,1的数量为0
那么说明这条边的所求边 
#include<bits/stdc++.h>
#define fo(i,j,n) for(register int i=j; i<=n; ++i) 
using namespace std;
const int N=300005,M=600005;
int n,a[300005],red,blue,ans;
int head[N],ver[M],Next[M],tot;
void add(int x, int y){
	ver[++tot]=y;
	Next[tot]=head[x], head[x]=tot;
}
// 统计 x 及其子孙红蓝个数 
pair<int, int> dfs(int x, int par){
	int r = (a[x]==1), b = (a[x]==2);  
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y==par)continue;
		pair<int, int> temp = dfs(y,x);
		// 如果子代净包含x或者y,说明是一条Good边 
		if(temp.first==red  && temp.second==0)ans++;
		if(temp.first==0 && temp.second==blue)ans++;
		r += temp.first;
		b += temp.second; 
	} 
	return make_pair(r,b);
}
int main(){
	scanf("%d",&n);
	fo(i,1,n){
		scanf("%d",&a[i]);
		if(a[i]==1)red++;
		if(a[i]==2)blue++;
	}
	int x,y;
	fo(i,1,n-1){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs(1,-1);
	cout<<ans;
	return 0;
}
F2

lca+dp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值