CF1313D Happy New Year

传送门


这题挺妙的,我题解都看了半天。


题目的意思是有\(n\)个操作,每个操作可以让区间\([L_i,R_i]\)的数加1,问在每个操作最多用一次的前提下,序列中奇数最多的个数。


这么一看确实不知道怎么做,但是题中还给了一个限制,就是区间最多会重叠\(k(k \leqslant 8)\)层。这就启发我们说不定可以用状压dp。


首先把\(n\)个区间拆成\((L_i, 1)\)\((R_i + 1, -1)\)\(2n\)个点,然后考虑每一段怎么怎么求。令\(dp[i][S]\)表示从第\(1\)段到第\(i\)段,选了区间编号的集合为\(S\)时,奇数最多的个数。

对于区间编号,是可以在dp的时候处理出来的,详见代码。对于第\(i\)段和第\(i-1\)段,必然只有一个区间发生了变化(对于端点重合的部分,可以看成长度为\(0\)的一段),假设为第\(k\)个区间,那么就要分两种情况:

  1. \(i\)段是区间\(k\)的开始,那么对于每一个状态\(S\),只要枚举是否选择区间\(k\)即可,转移方程详见代码吧,好理解,但不太好写;
  2. \(i\)段是区间\(k\)的结束,那么对于每一个状态\(S\),首先要保证第\(k\)位为\(0\),然后也是枚举这个区间之前是否选了进行转移。

这样扫描到最后一段,所有的区间都结束了,因此答案就是\(dp[2n][0]\).

时间复杂度\(O(n \log n + 2^k n)\)\(n\log n\)是排序复杂度。


代码实现参考了洛谷的一篇题解,用的滚动数组。

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif
}

int n, m;
struct Node
{
	int pos, d;
	In bool operator < (const Node& oth)const
	{
		return pos < oth.pos || (pos == oth.pos && d < oth.d);
	}
}t[maxn << 1];

int dp[256], vis[10];

int main()
{
//	MYFILE();
	n = read(), read(), read(); m = n << 1; 
	for(int i = 1; i <= n; ++i) 
	{
		int L = read(), R = read();
		t[i] = (Node){L, i}, t[n + i] = (Node){R + 1, -i};
	}
	sort(t + 1, t + m + 1); t[m + 1].pos = t[m].pos;
	fill(dp + 1, dp + 256, -INF);
	for(int j = 1; j <= m; ++j)
	{
		int d = t[j].d, len = t[j + 1].pos - t[j].pos, k;
		if(d > 0)
		{
			for(int i = 0; i < 8; ++i)
				if(!vis[i]) {vis[k = i] = d; break;}
			for(int i = 255; i >= 0; --i)
				if((i >> k) & 1) dp[i] = dp[i ^ (1 << k)] + len * __builtin_parity(i);
				else dp[i] = dp[i] + len * __builtin_parity(i);
		}		//__builtin_pairty(i):i的二进制位如果有奇数个,返回1;否则返回0。 
		else
		{
			for(int i = 0; i < 8; ++i)
				if(vis[i] == -d) {vis[k = i] = 0; break;}
			for(int i = 0; i < 256; ++i)
				if((i >> k) & 1) dp[i] = -INF;
				else dp[i] = max(dp[i], dp[i ^ (1 << k)]) + len * __builtin_parity(i);
		}
	}
	write(dp[0]), enter;
	return 0;
}
posted @ 2021-09-24 15:24  mrclr  阅读(50)  评论(0编辑  收藏  举报