codeforces#1313D. Happy New Year(扫描线+dp)
题目链接:
https://codeforces.com/contest/1313/problem/D
题意:
有$n$个操作,每个操作可以使得一段区间的小朋友糖果数加一
如果所有操作都执行的话,每个小朋友最多得到$k$个糖果。得到糖果数为奇数的小朋友会很开心,求最多使多少个小朋友开心
分析:
这题有点难,所以思考了半个小时没思路就看了$cf$的官方题解,然后结合排行榜的代码,终于理解了这题的做法
这道题目用到了扫描线算法,有$2n$个事件,定义$dp[i][j]$为第$i$个事件时,在所有覆盖当前段的操作中,选择状态为$j$的操作集合。如果某位为1,那么对应的操作被选上了
这个转移有点特殊,所以不再赘述,解法可以看代码
我们可以考虑如果小朋友数量最大不是$1e9$,我们按照每个小朋友来$dp$,而不是按照事件来$dp$,那么我们可以为$dp[i][j]+1$如果$j$是奇数状态,否则不变,如果一个区间结束了,我们可以释放对应状态的标记,最后肯定释放了所有位置,所以结果就是$dp[0]$了
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (int i=(a);i<=(b);i++)
#define per(i,a,b) for (int i=(b);i>=(a);i--)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef long long ll;
typedef vector<int> VI;
typedef pair<int,int> PII;
const int maxn=1e5+7;
const int INF=0x3f3f3f3f;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
int sk[(1<<8)+7],sksz,n,m,k;
int dp[(1<<8)+7],idtocnt[maxn*2],used[(1<<8)+7];
struct Quer{
int x,type,id;
bool operator <(const Quer other)const{
if(x!=other.x)return x<other.x;
else return type<other.type;
}
}quer[maxn*2];
int main() {
scanf("%d %d %d",&n,&m,&k);
rep(i,1,n){
int l,r;
scanf("%d %d",&l,&r);
quer[i*2]={l,1,i};
quer[i*2-1]={r+1,-1,i};
}
//初始化dp数组和计算1的数量为奇数的二进制的集合
rep(i,0,(1<<k)-1){
dp[i]=-INF;
int tem=i,cnt=0;
while(tem){
if(tem%2)cnt++;
tem/=2;
}
if(cnt%2)sk[++sksz]=i;
}
dp[0]=0;
sort(quer+1,quer+1+2*n);
int prex=0;
rep(i,1,2*n){
Quer now=quer[i];
// cout<<now.x<<" "<<now.type<<endl;
rep(j,1,sksz){
if(dp[sk[j]]==-INF)continue;
dp[sk[j]]+=(now.x-prex);
}
prex=now.x;
if(now.type==1){
int cnt=0;
while(used[cnt])cnt++;
used[cnt]=1;
idtocnt[now.id]=cnt;
rep(mask,0,(1<<k)-1){
if((mask&(1<<cnt))==0)
dp[mask^(1<<cnt)]=dp[mask];
}
}else{
int cnt=idtocnt[now.id];
used[cnt]=0;
rep(mask,0,(1<<k)-1){
if(mask&(1<<cnt)){
dp[mask^(1<<cnt)]=max(dp[mask],dp[mask^(1<<cnt)]);
dp[mask]=-INF;
}
}
}
// rep(j,0,7)cout<<dp[j]<<" ";
// cout<<endl;
}
printf("%d\n",dp[0]);
return 0;
}

浙公网安备 33010602011771号