我在1313E的題解里提到本題非常難,是我出言不遜了。。我之前讀錯題意了。。
洛谷題目頁面傳送門 & CodeForces題目頁面傳送門
有\(m\)孩子,編號為\(1\sim m\)。有\(n\)條咒語,第\(i\)條可以讓編號在\([l_i,r_i]\)內的所有孩子得到\(1\)顆糖,每條咒語可以施或不施。要求最大化得到奇數顆糖的孩子的數量。輸出這個數量。保證若施所有咒語,那么每個孩子得到的糖的數量不超過\(s\)。
\(n\in \left[1,10^5\right],m\in\left[1,10^9\right],s\in[1,8]\)。
“保證若施所有咒語,那么每個孩子得到的糖的數量不超過\(s\)”這句話極其重要,它告訴我們每個孩子都在不超過\(s\)條咒語的區間里,即每個孩子都只有不超過\(s\)條咒語對他/她有效。這啟示我們對每個孩子枚舉所有對他/她有效的\(x\)條咒語哪些施、哪些不施的\(2^x\)種情況,從而狀壓DP。
DP之前,這個\(m\)的范圍一看就是要離散化。我們把\(\forall i\in[1,n],l_i-1,r_i\)這\(2n\)個數壓入即將sort
、unique
的1-indexed數列\(nums\)里,sort
、unique
之后,\(\forall i\in[1,|nums|]\),今后的數\(i\)就代表從前的區間\((nums_{i-1},nums_i]\),特殊地,\(nums_0=0\)。這樣可以保證今后的每個孩子代表的從前的區間內每個數所在咒語區間情況相同,因為咒語區間的端點代表所在咒語區間情況相同段的分界。但還有一個問題:有可能從前的某個后綴內的數都沒有被包含到任何今后的數所表示的區間里。很簡單,只要再往\(nums\)里加一個數\(m\)即可。時間復雜度\(\mathrm O(n\log n)\)。
接下來考慮DP。先預處理出0-indexed數列數組\(in\),\(in_i\)表示今后的數\(i\)所表示的從前的區間內每個孩子所在的咒語區間編號組成的數列(對於區間內每個孩子生成的數列都是一樣的),順序隨意。這樣一來,\(\forall i\in[1,n],\forall j\in[1,|nums|]\),若\(\forall k\in(nums_{j-1},nums_j],k\in[l_i,r_i]\),則咒語\(i\)對於今后的數\(j\)便有了一個新編號。顯然,預處理這個數列數組的時間復雜度為\(\mathrm O(ns)\)。
設\(dp_{i,j}\)表示考慮到今后的數\(i\),對於bitmask\(j\)滿足\(x\in j\)當且僅當施咒語\(in_{i,x}\)時,前\(nums_i\)個孩子得到奇數顆糖的最大的數量。顯然邊界是\(dp_{0,0}=0\),目標是\(\max\limits_i\{dp_{|nums|,i}\}\)。轉移時只需要注意\(1\)個限制:\(\{in_{i}\}\)和\(\{in_{i-1}\}\)可能交集不為空,此時那些交集里的咒語不能精神分裂,即\(\forall x\in\{in_{i}\}\cap\{in_{i-1}\}\),咒語\(x\)在\(j\)和轉移到的bitmask\(k\)里狀態必須相等。
那么狀態轉移方程是:
其中\(cantrs(i,j,k)\)表示\(dp_{i,j}\)轉移到\(dp_{i-1,k}\)滿足上述限制。
這樣直接按方程來轉移的話,狀態數\(\mathrm O(2^sn)\),每次轉移對象\(\mathrm O(2^s)\),\(cantrs\)判斷\(\mathrm O(s)\),所以時間復雜度是\(\mathrm O(4^sns)\),太大了。顯然我們最多只能接受\(\mathrm O(2^sn)\)的。
注意到若對於\(2\)個狀態\(j_1,j_2\),\(\forall x\in\{in_{i}\}\cap\{in_{i-1}\}\),咒語\(x\)在\(j_1,j_2\)內的狀態相同,那么\(dp_{i,j_1},dp_{i,j_2}\)能轉移到的合法狀態集合是完全相同的。於是設\(and_i=\{in_{i}\}\cap\{in_{i-1}\}\),我們可以按\(and\)內所有元素的\(2^{|ans|}\)種狀態分類,這樣對每類統一找合法轉移對象,計算對於此類中所有bitmask\(j\),\(\max\limits_{cantrs(i,j,k)}\{dp_{i-1,k}\}\),然后對於此類中所有狀態\(\mathrm O(1)\)計算DP值。但是復雜度依然沒有變。
要想改變復雜度,最重要的是去除\(cantrs\)判斷。注意到\(\forall k\),\(dp_{i-1,k}\)能且僅能轉移到\(1\)個狀態,於是我們若能找到命中率\(100\%\)且不用判斷的找合法轉移對象方式就能保證\(\mathrm O(2^sn)\)的復雜度。這非常簡單,只需要將每個\(k\)拆成\(\in and\)和\(\notin and\)這\(2\)部分,對於每類,直接固定\(\in and\)的部分,枚舉\(\notin and\)的部分即可。但是實現起來並不會那么順利,因為\(2\)部分分別可能在\(k\)中不連續,無法直接用位運算\(\mathrm O(1)\)合並,於是我們可以預處理,對於\(2\)個部分都給此部分每種狀態編個號,並記錄它在\(k\)中的狀態,最后將\(2\)部分在\(k\)中的狀態\(\mathrm{or}\)起來即可得到\(k\)。對於每類,現在已經找到所有合法轉移對象,即算出了對於此類中所有bitmask\(j\),\(\max\limits_{cantrs(i,j,k)}\{dp_{i-1,k}\}\),現在只需再命中率\(100\%\)且不用判斷地找到此類中所有狀態(方法和上述找轉移對象類似),即可\(\mathrm O(1)\)得到每個\(dp_{i,j}\)的值。
最終總復雜度為\(\mathrm O(n\log n)+\mathrm O(ns)+\mathrm O(2^sn)=\mathrm O(n(\log n+2^s))\),輕微卡常,但不用在意。
下面上代碼:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
int lowbit(int x){return x&-x;}
int lbt(int x){return __builtin_ctz(x);}
const int N=100000;
int n/*咒語數*/,m/*孩子數*/,s/*每個孩子最多在的咒語區間數量*/;
int l[N+1],r[N+1];//咒語區間
vector<int> nums;//離散化數組
void discrete(){//離散化
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=n;i++)
l[i]=lower_bound(nums.begin(),nums.end(),l[i])-nums.begin(),
r[i]=lower_bound(nums.begin(),nums.end(),r[i])-nums.begin();
}
vector<int> in[3*N+1];//所在咒語區間編號數列數組
vector<int> dp[3*N+1];//DP數組
int main(){
cin>>n>>m>>s;
nums.pb(0);nums.pb(m);
for(int i=1;i<=n;i++)scanf("%d%d",l+i,r+i),nums.pb(l[i]-1),nums.pb(r[i]);
discrete();
for(int i=1;i<=n;i++)for(int j=l[i];j<=r[i];j++)in[j].pb(i);//預處理
dp[0].resize(1,0);//邊界
for(int i=1;i<nums.size();i++){
// printf("%d dping\n",i);
// printf("\tin=",i);for(int j=0;j<in[i].size();j++)cout<<in[i][j]<<" ";puts("");
dp[i].resize(1<<in[i].size());//決定dp[i]元素個數
vector<pair<int,int> > _and;//對於所有x in {in[i]} cap {in[i-1]},(x在in[i]中的編號,x在in[i-1]中的編號)組成的二元組序列
for(int j=0;j<in[i].size();j++)for(int k=0;k<in[i-1].size();k++)if(in[i][j]==in[i-1][k])_and.pb(mp(j,k));//預處理and
// printf("\t_and=");for(int j=0;j<_and.size();j++)printf("(%d,%d) ",_and[j].X,_and[j].Y);puts("");
vector<int> nin_and1/*j中不在and中的部分*/,nin_and2/*k中不在and中的部分*/;
for(int j=0,k=0;j<in[i].size();j++)if(k>=_and.size()||(j==_and[k].X?k++,false:true))nin_and1.pb(j);//預處理nin_and1
for(int j=0,k=0;j<in[i-1].size();j++)if(k>=_and.size()||(j==_and[k].Y?k++,false:true))nin_and2.pb(j);//預處理nin_and2
// printf("\tnin_and1=");for(int j=0;j<nin_and1.size();j++)cout<<nin_and1[j]<<" ";puts("");
// printf("\tnin_and2=");for(int j=0;j<nin_and2.size();j++)cout<<nin_and2[j]<<" ";puts("");
vector<int> chg1(1<<_and.size(),0),chg2(1<<nin_and1.size(),0),chg3(1<<_and.size(),0),chg4(1<<nin_and2.size(),0);
for(int j=1;j<chg1.size();j++)chg1[j]=chg1[j^lowbit(j)]|1<<_and[lbt(j)].X;//預處理j中在and中的部分的每種狀態在j中的狀態
for(int j=1;j<chg2.size();j++)chg2[j]=chg2[j^lowbit(j)]|1<<nin_and1[lbt(j)];//預處理j中不在and中的部分的每種狀態在j中的狀態
for(int j=1;j<chg3.size();j++)chg3[j]=chg3[j^lowbit(j)]|1<<_and[lbt(j)].Y;//預處理k中在and中的部分的每種狀態在j中的狀態
for(int j=1;j<chg4.size();j++)chg4[j]=chg4[j^lowbit(j)]|1<<nin_and2[lbt(j)];//預處理k中不在and中的部分的每種狀態在j中的狀態
for(int j=0;j<chg1.size();j++){//枚舉類
int mx=0;//對於此類中所有bitmask j,max[cantrs(i,j,k)]{dp[i-1][k]}
for(int k=0;k<chg4.size();k++)mx=max(mx,dp[i-1][chg3[j]|chg4[k]]);//算mx
for(int k=0;k<chg2.size();k++)dp[i][chg1[j]|chg2[k]]=mx+__builtin_parity(chg1[j]|chg2[k])*(nums[i]-nums[i-1]);//轉移此類中的每個狀態
}
// for(int j=0;j<dp[i].size();j++)printf("\tdp[%d]=%d\n",j,dp[i][j]);
}
cout<<*max_element(dp[nums.size()-1].begin(),dp[nums.size()-1].end());//目標
return 0;
}