比赛时没做出来orz
剩下一段时间去hack了
不过当时估计也做不出了
好难啊
题目
#include
#define ll long long
#define N 200015
#define M 1<<10
#define lowbit(i) ((i)&(-i))
using namespace std;
int n,m,k,maxn,p[N],l[N],r[N];
int sm[M],st[M],vis[M],f[2][M],val[M];
vector<int> mem[N];
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;++i) {
scanf("%d%d",&l[i],&r[i]);
p[++maxn] = l[i];
p[++maxn] = r[i]+1;
}
sort(p+1,p+maxn+1);
maxn = unique(p+1,p+maxn+1)-p-1;
for(int i = 1;i <= n;++i){
int le = lower_bound(p+1,p+maxn+1,l[i])-p,
ri = lower_bound(p+1,p+maxn+1,r[i]+1)-p;
for(int j = le;j < ri;++j)
mem[j].push_back(i);
}
int now = 0,next = 1;
for(int i = 1;i < maxn;++i){
int s0 = mem[i-1].size(),s1 = mem[i].size();
fill(f[next],f[next]+(1<<s1),0);
fill(sm,sm+(1<<s1),0);
fill(st,st+(1<<s0),0);
fill(val,val+(1<<s0),-1);
int tp = 0;
for(int j = 0;j < s1;++j)
for(int k = 0;k < s0;++k)
if(mem[i-1][k]==mem[i][j])
sm[1<<j] = (1<<k),vis[k] = i;
for(int j = 0;j < s0;++j)
if(vis[j]!=i)
st[1<<tp] = (1<<j),tp++;
for(int j = 0;j < (1<<tp);++j)
st[j] = st[lowbit(j)]|st[j^lowbit(j)];
for(int j = 0;j < (1<<s1);++j){
sm[j] = sm[lowbit(j)]|sm[j^lowbit(j)];
if(val[sm[j]] < 0){
for(int k = 0;k < (1<<tp);++k){
val[sm[j]] = max(val[sm[j]],f[now][sm[j]|st[k]]);
}
}
f[next][j] = val[sm[j]];
if(__builtin_popcount(j)&1) f[next][j] += p[i+1]-p[i];
}
now ^= 1;next ^= 1;
}
int res = 0;
for(int i = 0;i < (1<<mem[maxn-1].size());++i)
res = max(res,f[now][i]);
printf("%d\n",res);
return 0;
}
因为我自己不会做,题解没看懂,所以代码基本来自Rank3 Crazy_Diamond
给m个点,有n种操作,每一个可以给一个区间整体+1,要使得值为奇数的点最多,输出最多有多少个。
扫描线+状压dp
扫描线:把区间端点排序再离散化
for(int i = 1;i <= n;++i) {//输入
scanf("%d%d",&l[i],&r[i]);
p[++maxn] = l[i];
p[++maxn] = r[i]+1;
}
sort(p+1,p+maxn+1);//排序
maxn = unique(p+1,p+maxn+1)-p-1;//去重
for(int i = 1;i <= n;++i){
int le = lower_bound(p+1,p+maxn+1,l[i])-p,
ri = lower_bound(p+1,p+maxn+1,r[i]+1)-p;
//离散化
for(int j = le;j < ri;++j)
mem[j].push_back(i);
/*
这里是记录每一个点被哪些区间覆盖
*/
}
题目中给出了一个点最多有k个区间覆盖,k <= 8 于是就可以对这几个区间进行状压。
f [ i ] [ j ] f[i][j] f[i][j]表示处理到前i个端点,覆盖状态为j时的答案。
int now = 0,next = 1;
/*
这里是滚动数组。
大佬在写我不会写的题时还能有时间加优化orz
*/
for(int i = 1;i < maxn;++i){//遍历
int s0 = mem[i-1].size(),s1 = mem[i].size();
fill(f[next],f[next]+(1<<s1),0);
fill(sm,sm+(1<<s1),0);//sm是当前这个点仍然覆盖的区间对应上一个状态的映射
fill(st,st+(1<<s0),0);//st是上一个点有,但是当前这个点没有覆盖的区间对应上一个状态的映射
fill(val,val+(1<<s0),-1);
int tp = 0;
for(int j = 0;j < s1;++j)
for(int k = 0;k < s0;++k)
if(mem[i-1][k]==mem[i][j])//如果上一个点也有这个区间
sm[1<<j] = (1<<k),vis[k] = i;// 把j映射到k
for(int j = 0;j < s0;++j)
if(vis[j]!=i)//如果这个区间只覆盖上一个
st[1<<tp] = (1<<j),tp++;//把tp映射到j
for(int j = 0;j < (1<<tp);++j)
st[j] = st[lowbit(j)]|st[j^lowbit(j)];
/*
lowbit是取第一个二进制位1
这里相当于一个递推
举个例子:
st[1000101] = st[1000100]+ st[1] = st[1000000] + st[100] + st[1]
*/
for(int j = 0;j < (1<<s1);++j){
sm[j] = sm[lowbit(j)]|sm[j^lowbit(j)];//和上面一样
if(val[sm[j]] < 0){
for(int k = 0;k < (1<<tp);++k){
val[sm[j]] = max(val[sm[j]],f[now][sm[j]|st[k]]);
/*
j是当前的状态
sm[j]是当前状态到上一个状态的映射
st[k]|sm[j]就是上一个状态
*/
}
}
f[next][j] = val[sm[j]];
if(__builtin_popcount(j)&1) f[next][j] += p[i+1]-p[i];
/*
__builtin_popcount是求j中1的个数
如果是奇数
更新答案
*/
}
now ^= 1;next ^= 1;//滚动数组
}
关于上面提到的“映射”,这里再解释一下。
我们不难发现,在点 i − 1 i-1 i−1排第一位的区间在点 i i i不一定排第一位,而 f [ n o w ] f[now] f[now]的状态是按照 i − 1 i-1 i−1的顺序记录的。
所以我们用两个数组把他转化成上一个状态的顺序