TJ - [CF1313D] Happy New Year

[CF1313D] Happy New Year

难度:2500

2s,500MB

一,题目:

题目大意:

给你一个长度为m的序列a,初始值为0,现在有n个操作,第i操个操作是将区间[Li,Ri]内的元素的值都加1,你可以选择进行任意个操作,但每个操作都只能进行一次。问你最多可以使得多少个元素的值为奇数。

题目会保证若将所有操作都进行后,每个元素最大为k

数据范围:

1n1×105,1m1×109,1k8

读入格式:

第一行n,m,k

接下来的n行,第i+1行为Li,Ri

样例输入:
3 5 3
1 3
2 4
3 5
样例输出:
4

二,solution:

先把题意转化一下:

我们可以把a抽象为一个长度为m的数轴,每个操作其实就是用一个线段覆盖[Li,Ri]这个区间,问你最多有几个点被覆盖被覆盖了奇数次,如下图(样例):

看到这个k8,(代表一个点最多被k条线覆盖),自然而然的想到状压DP

不妨设f[i][S]表示第i个点被集合为S的这些线覆盖住。

但是,这个集合S中每一位所代表的点是一直在变化的,怎么办呢?

我们设vis[i]表示当前S的第i位代表的线段的编号,在每次有线段加入\退出的时候,利用vis进行更改即可。

加入:

for(int j=0;j<k;j++){
if(vis[j]==0){
now=j;//now表示当前处理到的线段对应的二进制位
vis[j]=id;//标记一下,id
break;
}
}

退出:

for(int j=0;j<k;j++){
if(-id==vis[j]){
vis[j]=0;//清空
now=j;//now表示当前处理到的线段对应的二进制位
break;
}
}

但是m最大有109,时间复杂度为O(M×2k),绝对会TLE,若不进行滚动数组优化的话空间也受不了。

需要优化

我们发现,操作的个数是1×105,若把上面的那个M换为N,就可以接受了。

不妨把每个操作抽象为两个二元组扫描线(Li,i)(Ri+1,i)。(第二位为整数表示是一个操作的开头,否则为结尾),分别表示从这个点开始就多了/少了一条线段。

这样子,最多会有2×n个点,空间复杂度可以接受。

我们在把这些扫描线排序后,很明显,在相邻的两条扫描线中间的元素被覆盖的情况是可以用同一个二进制数表示的。

f[i][S]表示第i个扫描线,此时使用了集合为S的这些线段。(则代表当前这个扫描线到下一条扫描线之间的左闭右开的区间被覆盖情况都为S)。

好了,接下来正儿八经的推式子:

len表示当前这个扫描线到下一条扫描线的长度,now表示当前的扫描线在S中对应的二进制位,ask(S)表示S二进制下是否有奇数个一。

若这个扫描线为线段的左端点,则:

f[i][j]={f[i1][j]+lenask(j)j二进制下第now位为0f[i1][j(1<<now)]+lenask(j)j二进制下第now位为1

若这个扫描线为线段的右端点,则:

f[i][j]={max{f[i1][j],f[i1][j(1<<now)]}+lenask(j)j二进制下第now位为0INFj二进制下第now位为1

初始化时,其他f[i][j]都为INFf[0][0]=0

f[n2][0]即为所求,因为最后一个扫描线一定是代表右端点,则他一定不会被任何一条线覆盖。

好了,最后注意一下,将扫描线进行排序时,同一个点上,代表右端点的扫描线因该在左端点的扫描线前。

另外,这道题其实可以用将第i维优化掉或者用滚动数组,感兴趣的可以写一写。

代码:

#include<bits/stdc++.h>
#define ll long long
#define m_p make_pair
using namespace std;
const int N=1e5+5,K=8;
int n,m,k;
int f[N*2][(1<<K)];//到第i条线时,用了的线的集合为j,最多高兴的孩子的数量
vector<pair<int,int> > a;
int vis[K];
inline int ask(int x){//x二进制下是否有奇数个一。
int sum=0;
while(x){
if(x&1) sum++;
x>>=1;
}
return (sum&1);
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int l,r;
for(int i=1;i<=n;i++){
scanf("%d%d",&l,&r);
a.push_back(m_p(l,i));
a.push_back(m_p(r+1,-i));//处理为两条扫描线
}
sort(a.begin(),a.end());//进行排序
//初始化:
memset(f,0xcf,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n*2;i++){
int id=a[i-1].second;
int len= (a[i-1].first==a[n*2-1].first) ? 0 : a[i].first-a[i-1].first;//注意,最后一个点的扫描线代表的区间长度为0
int now;//当前的扫描线在对应的二进制位
if(id<0){//右端点
for(int j=0;j<k;j++){
if(-id==vis[j]){
vis[j]=0;
now=j;
break;
}
}
for(int j=0;j<(1<<k);j++){
if((j>>now)&1) f[i][j]=0xcfcfcfcf;
else f[i][j]=max(f[i-1][j],f[i-1][j^(1<<now)])+len*ask(j);
}
}
else{//左端点
for(int j=0;j<k;j++){
if(vis[j]==0){
now=j;
vis[j]=id;
break;
}
}
for(int j=0;j<(1<<k);j++){
if((j>>now)&1) f[i][j]=f[i-1][j^(1<<now)]+len*ask(j);
else f[i][j]=f[i-1][j]+len*ask(j);
}
}
}
cout<<f[n*2][0];//最后一个扫描线一定是代表右端点,则他一定不会被任何一条线覆盖。
return 0;
}
posted @   123456xwd  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示