集训笔记
二分中位数,然后把大于等于 \(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;
}
二分最大值与最小值的差,\(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;
}
二分最多可以做成多少个,设二分的数量为 \(n\),并且第一种方案有 \(m\) 个,可以列出两个不等式:
\(b\times n+a\times (n-m)\le y\)
并把这两个不等式处理一下变成:
\(\frac{x-a\times n}{b-a}\ge m\)
注意 \(k\) 还要满足:
还要注意:
\(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;
}
原题代码:
这道题是一道很有价值的题,可以用两种方法来做。
一、反悔贪心
先把能阻止的等级阻止,如果不能阻止就要开始反悔,把耗费最大的阻止等级弹出,加入新的阻止等级,用大根堆维护。
代码
#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;
}
设答案为 \(k\),那么前 \(k\) 行和前 \(k\) 列都会被染到,还有 \(2(n-k)-1\) 条对角线没有染到,所以 \(k\ge 2(n-k)-1\),所以 \(k=\lceil\frac{2n-1}{3}\rceil\)。然后考虑怎么放置,要把 \(2(n-k)-1\) 条对角线都占满,所以考虑 \(/\) 斜着放,具体可以参考一下这张抽象的图:

细节看代码
代码
#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;
}
首先千万不要想贪心,其实也可以试一下,也就是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;
}
经典题,类似于 \(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;
}
代码
#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 的题基本都是跑一遍最短路,或者跑两遍,一遍正一遍反,且多数都需要构造一个虚构的点,把题中的一些信息作为边权。

浙公网安备 33010602011771号