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