题目链接:
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 ",dp[0]); return 0; }