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))(nlog 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;
}
原文地址:https://www.cnblogs.com/mrclr/p/15330600.html