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\)个区间,那么就要分两种情况:
- 第\(i\)段是区间\(k\)的开始,那么对于每一个状态\(S\),只要枚举是否选择区间\(k\)即可,转移方程详见代码吧,好理解,但不太好写;
- 第\(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;
}