集训笔记

CF1486D Max Median(二分)

二分中位数,然后把大于等于 \(x\) 的赋值成 \(1\),小于 \(x\) 的赋值成 \(-1\),然后求前缀和,枚举右端点,保证区间大于等于 \(k\),并且维护一个前缀 \(\max\),如果有区间大于 \(0\),返回 \(1\),否则返回 \(0\)

重点:赋值成 \(1\)\(-1\) 求前缀和是一种很常见的思路。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5;
int n;
long long c[N],k;
bool check(long long x){
	long long s=0,h=0;
    for(int i=1;i<=n;i++){
    	long long cc=c[i];
        if(h){
            long long need=x-h;
            if(cc>=need){
                s++;
                cc-=need;
                h=0;
            }
            else h=0;
        }
        s+=cc/x;
        h=cc%x;
        if(s>=k) return 1;
	}
	return 0;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%lld",&n,&k);
        long long sum=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&c[i]);
            sum+=c[i];
        }
        if(sum<=k){
            printf("0\n");
            continue;
        }
        long long l=1,r=sum/k;
        while(l<r){
            long long mid=l+r+1>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        printf("%lld\n",l*k);
    }
    return 0;
}

CF883I Photo Processing(二分)

二分最大值与最小值的差,\(f_{i}\) 表示在 \(1\)~\(i\) 中是否可以找到一个合法的方案,每次 \(\operatorname{check}\) 只需要求一下 \(f\),并判断一下 \(f_{n}\) 是否等于 \(1\) 就可以。具体细节看代码。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,k;
int a[N],f[N];
bool check(int x){
    int j=1;
    for(int i=1;i<=n;i++) f[i]=0;
    f[0]=1;
    for(int i=k;i<=n;i++){
        while(a[i]-a[j]>x&&j<i) j++;
        while(j<=i-k+1){
            if(f[j-1]){
                f[i]=1;
                break;
            }
            j++;
        }
    }
    return f[n];
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sort(a+1,a+n+1);
    int l=0,r=a[n]-a[1];
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    printf("%d",l);
    return 0;
}

CF1538G Gift Set(二分)

二分最多可以做成多少个,设二分的数量为 \(n\),并且第一种方案有 \(m\) 个,可以列出两个不等式:

$a\times n+b\times (n-m)\le x$

\(b\times n+a\times (n-m)\le y\)

并把这两个不等式处理一下变成:

$\frac{x-b\times n}{a-b}\le m$

\(\frac{x-a\times n}{b-a}\ge m\)

注意 \(k\) 还要满足:

$0\le k\le n$

还要注意

\(1.\) 因为除数不能为 \(0\),所以 \(a=b\) 时,需要特判
\(2.\) 一定要满足 \(a>b\) 不然不等式的符号方向可能发生变化。

代码
#include<bits/stdc++.h>
using namespace std;
long long x,y,a,b;
bool check(long long n){
    long long l=ceil(1.0*(y-a*n)/(b-a)),r=floor(1.0*(x-b*n)/(a-b));
    return max(l,0ll)<=min(r,n);
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld%lld",&x,&y,&a,&b);
        if(a==b){
            printf("%lld\n",min(x,y)/a);
            continue;
        }
        if(a<b) swap(a,b);
        long long l=0,r=1e9;
        while(l<r){
            int mid=l+r+1>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        printf("%lld\n",l);
    }
    return 0;
}

模拟赛一T4

原题代码:

CF1987D World is Mine

这道题是一道很有价值的题,可以用两种方法来做。

一、反悔贪心

先把能阻止的等级阻止,如果不能阻止就要开始反悔,把耗费最大的阻止等级弹出,加入新的阻止等级,用大根堆维护。

代码
#include<bits/stdc++.h>
using namespace std;
int a[5005],b[5005];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) b[i]=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			b[a[i]]++;
		}
		sort(a+1,a+n+1);
		int len=unique(a+1,a+n+1)-a-1;
		priority_queue<int> q;
		long long sum=0;
		for(int i=1;i<=len;i++){
			sum+=b[a[i]];//sum+=b[a[i]+1; 
			q.push(b[a[i]]);//q.push(b[a[i]+1);
			if(sum>i-q.size()){//if(sum>i)
				sum-=q.top();
				q.pop();
			}
		}
		printf("%d\n",len-q.size());
	}
	return 0;
}

为什么要 \(sum>i-q.size()\) 呢?因为当前阻止了 \(q.size()\) 个等级,所以 wowo 实际上走了 \(i-q.size()\) 的个等级,所以 \(moumou\) 也走了这些,注释掉的写法也是同理,相当于移项了一下。

一、DP

\(f_{i,j}\) 表示前 \(i\) 个等级,wowo 看来 \(j\) 个展,moumou 最多能走多少步

代码
#include<bits/stdc++.h>
using namespace std;
int a[5005],b[5005];
int f[5005][5005]; 
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) b[i]=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			b[a[i]]++;
		}
		sort(a+1,a+n+1);
		int len=unique(a+1,a+n+1)-a-1;
		for(int i=1;i<=len;i++){
			f[i][0]=f[0][i]=-0x3f3f3f3f;
		}
		for(int i=1;i<=len;i++){
			for(int j=1;j<=i;j++){
				f[i][j]=-0x3f3f3f3f;
				if(f[i-1][j-1]!=-0x3f3f3f3f) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
				if(b[a[i]]<=f[i-1][j]) f[i][j]=max(f[i][j],f[i-1][j]-b[a[i]]);
			}
		}
		for(int i=1;i<=len;i++){
			if(f[len][i]!=-0x3f3f3f3f){
				printf("%d\n",i);
				break;
			}
		}
	}
	return 0;
}

灯带(构造)

只要把一个颜色放完,剩下的随便放就可以,对于每个 \(l\)\(r\),考虑该怎么放,把颜色的个数从小到大排序,分别为 \(c_{1}\)\(c_{2}\)\(c_{3}\),先判断可不可以只用 \(c_{1}\) 填满,然后考虑 \(c_{1}\)\(c_{2}\),不可以的话就考虑 \(c_{1}\)\(c_{3}\),最后考虑 \(c_{2}\)\(c_{3}\),细节看代码。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char s[N];
struct Node{
	int l,r;
}a[N];
struct node{
	int s;
	char c;
}e[5];
bool cmp(Node a,Node b){
	return a.l<b.l;
}
bool cmp1(node a,node b){
	return a.s<b.s; 
}
int main(){
	int t;
	scanf("%d",&t); 
	while(t--){
		int n,m,r,g,b;
		scanf("%d%d%d%d%d",&n,&m,&r,&g,&b);
		e[1].s=r;
		e[1].c='R';
		e[2].s=g;
		e[2].c='G';
		e[3].s=b;
		e[3].c='B';
		sort(e+1,e+4,cmp1);
		for(int i=1;i<=m;i++){
			scanf("%d%d",&a[i].l,&a[i].r); 
		}
		sort(a+1,a+m+1,cmp);
		for(int i=1;i<=n;i++) s[i]='#';
		int flag=1;
		for(int i=1;i<=m;i++){
			int f=0; 
			if(e[1].s>=a[i].r-a[i].l+1){
				for(int j=a[i].l;j<=a[i].r;j++){
					s[j]=e[1].c;
				}
				e[1].s-=(a[i].r-a[i].l+1);
				f=1;
			}
			else{
				if(e[1].s){
					if(e[1].s+e[2].s>=a[i].r-a[i].l+1){
						int k=1;
						for(int j=a[i].l;j<=a[i].r;j++){
							while(!e[k].s) k++;
							s[j]=e[k].c;
							e[k].s--;
						}
						f=1;
					}
					else if(e[1].s+e[3].s>=a[i].r-a[i].l+1){
						int k=1;
						for(int j=a[i].l;j<=a[i].r;j++){
							if(!e[k].s) k=3;
							s[j]=e[k].c;
							e[k].s--;
						}
						f=1;
					}
					else if(e[2].s+e[3].s>=a[i].r-a[i].l+1){
						int k=2;
						for(int j=a[i].l;j<=a[i].r;j++){
							while(!e[k].s) k++;
							s[j]=e[k].c;
							e[k].s--;
						}
						f=1;
					}
				}
				else{
					if(e[2].s){
						if(e[2].s>=a[i].r-a[i].l+1){
							for(int j=a[i].l;j<=a[i].r;j++){
								s[j]=e[2].c;
							}
							e[2].s-=(a[i].r-a[i].l+1);
							f=1;
						}
						else if(e[2].s+e[3].s>=a[i].r-a[i].l+1){
							int k=2;
							for(int j=a[i].l;j<=a[i].r;j++){
								while(!e[k].s) k++;
								s[j]=e[k].c;
								e[k].s--;
							}
							f=1;
						}
					}
					else if(e[3].s>=a[i].r-a[i].l+1){
						for(int j=a[i].l;j<=a[i].r;j++){
							s[j]=e[3].c;
						}
						e[3].s-=(a[i].r-a[i].l+1);
						f=1;
					}
				}
			}
			if(!f){
				flag=0;
				break;
			}
		}
		if(!flag){
			printf("-1\n");
			continue;
		}
		int k=1;
		for(int i=1;i<=n;i++){
			if(s[i]!='#') continue;
			while(!e[k].s) k++;
			s[i]=e[k].c;
			e[k].s--;
		}
		for(int i=1;i<=n;i++){
			printf("%c",s[i]);
		}
		printf("\n");
	}
	return 0;
}

CF1667C Half Queen Cover(构造)

设答案为 \(k\),那么前 \(k\) 行和前 \(k\) 列都会被染到,还有 \(2(n-k)-1\) 条对角线没有染到,所以 \(k\ge 2(n-k)-1\),所以 \(k=\lceil\frac{2n-1}{3}\rceil\)。然后考虑怎么放置,要把 \(2(n-k)-1\) 条对角线都占满,所以考虑 \(/\) 斜着放,具体可以参考一下这张抽象的图:

screenshot-20260804-000913

细节看代码

代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    scanf("%d",&n);
    int k=(int)ceil((2*n-1)/3.0);
    printf("%d\n",k);
    if(n==1){
        printf("1 1");
        return 0;
    }
    int len=n-k,j=len,t=0;
    for(int i=1;i<=k;i++){
        if(len>=0&&j>0&&j+t<=k) printf("%d %d\n",j+t,i);
        else printf("%d %d\n",i,i);
        j--;
        if(!j){
            t+=len;
            j=--len;
        }
    }
    return 0;
}

CF1651D Nearest Excluded Points(图论三)

首先可以计算出来上下左右四个不是给出的点的答案,然后用它们去扩展,其他周边都有点的位置,然后把这些位置的答案更新成扩展到它的点的答案,最后加入队列,继续扩展,细节看代码。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int x[N],y[N];
int tx[N],ty[N],dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
bool st[N];
map<pair<int,int>,int> mp;
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x[i],&y[i]);
        mp[{x[i],y[i]}]=i;
    }
    queue<int> q;
    for(int i=1;i<=n;i++){
    	for(int j=0;j<4;j++){
    		if(!mp[{x[i]+dx[j],y[i]+dy[j]}]){
    			tx[i]=x[i]+dx[j];
    			ty[i]=y[i]+dy[j];
    			st[i]=1;
    			q.push(i);
			}
		}
	}
	while(q.size()){
		int t=q.front();
		q.pop();
		for(int i=0;i<4;i++){
			if(mp[{x[t]+dx[i],y[t]+dy[i]}]){
				int k=mp[{x[t]+dx[i],y[t]+dy[i]}];
				if(!st[k]){
					st[k]=1;
					tx[k]=tx[t];
					ty[k]=ty[t];
					q.push(k);
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		printf("%d %d\n",tx[i],ty[i]);
	}
    return 0;
}

CF1702E Split Into Two Sets

首先千万不要想贪心,其实也可以试一下,也就是wrong answer expected YES, found NO [6930th token]而已,错误样例如下:
6
1 2
5 4
2 3
4 3
5 6
6 1

我们可以发现,如果成立的话,那么这个图一定是由若干个点数为偶数的环组成的,然后这道题就完事了,细节看代码。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int e[2*N],ne[2*N],h[N],idx;
int st[N],d[N],c[N],cnt; 
void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int fa){
	st[u]=1;
	c[++cnt]=u;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==fa||st[v]) continue;
		dfs(v,u);
	}
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
	    scanf("%d",&n);
	    idx=0;
	    for(int i=1;i<=n;i++) h[i]=-1,st[i]=0,d[i]=0; 
	    for(int i=1;i<=n;i++){
	    	int a,b;
	        scanf("%d%d",&a,&b);
	        add(a,b);
	        add(b,a);
	        d[a]++;
	        d[b]++;
	    }
	    int f=1;
	    for(int i=1;i<=n;i++){
	    	if(d[i]>2){
	    		f=0;
	    		break;
			}
		}
		if(!f){
			printf("NO\n");
			continue;
		}
	    for(int i=1;i<=n;i++){
	    	if(!st[i]){
	    		cnt=0;
	    		dfs(i,0);
	    		int flag=1;
	    		for(int j=1;j<=cnt;j++){
	    			if(d[i]!=2) flag=0;
				}
				if(flag&&cnt%2){
					f=0;
					break;
				}
			}
		}
		if(f) printf("YES\n");
		else printf("NO\n");
	}
    return 0;
}

CF911E Stack Sorting(数据结构三)

我写的题解

CF1312E Array Shrinking

经典题,类似于 \(2048\)

代码
#include<bits/stdc++.h>
using namespace std;
int a[505],f[505][505],dp[505][505];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        f[i][i]=1;
        dp[i][i]=a[i];
    }
    for(int l=2;l<=n;l++){
        for(int i=1;i<=n-l+1;i++){
            int j=i+l-1;
            f[i][j]=l; 
            for(int k=i;k<j;k++){
                if(dp[i][k]&&dp[i][k]==dp[k+1][j]){
                	f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]-1);
                	dp[i][j]=dp[i][k]+1;
				}
				else f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
            }
        }
    }
    printf("%d",f[1][n]);
    return 0;
}

CF274B Zero Tree(dp三)

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int e[N*2],ne[N*2],h[N],idx;
long long c[N],f[N],g[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int fa){
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(v==fa) continue;
        dfs(v,u);
        f[u]=max(f[u],f[v]);
        g[u]=max(g[u],g[v]);
    }
    if(c[u]+f[u]-g[u]<0) f[u]-=c[u]+f[u]-g[u];
    else g[u]+=c[u]+f[u]-g[u];
}
int main(){
    int n;
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&c[i]);
    }
    dfs(1,0);
    printf("%lld",f[1]+g[1]);
    return 0;
}

P14540 [IO 2024 #3] 岛屿追逐(结营测E)

很典的一道 dij 题,赛时居然没想出来,光想着骗分了,根本没怎么想正解

只需要建一个假设的点 \(0\),然后连从 \(i\)\(0\) 的边权为 \(a_{i}\) 的边,并把题中给的边边权连为 \(2\times w\),然后跑从 \(0\) 到每个点的最短路。

注意long long 问题,和数组大小,连完 \(0\) 的边需要开 \(4\) 倍数组。

经验dij 的题基本都是跑一遍最短路,或者跑两遍,一遍正一遍反,且多数都需要构造一个虚构的点,把题中的一些信息作为边权。

posted @ 2026-07-29 19:57  yushihan  阅读(17)  评论(0)    收藏  举报