codeforces1313D Happy New Year 题解

比赛时没做出来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 i1排第一位的区间在点 i i i不一定排第一位,而 f [ n o w ] f[now] f[now]的状态是按照 i − 1 i-1 i1的顺序记录的。
所以我们用两个数组把他转化成上一个状态的顺序

你可能感兴趣的:(codeforces1313D Happy New Year 题解)