Part1 一點雜題
agc034_e Complete Compress
枚舉最終這些棋子被移到了哪個節點,把這個終點拿出來作為根\(root\)。
我們一次操作一定是把兩個棋子各向根移動一步,這需要這兩個棋子不是“祖先-后代”的關系。則一個節點\(u\)需要操作的次數是\(dis(u,root)\)。我們把每個初始時有棋子的節點\(u\)看做\(dis(u,root)\)個小石子。考慮根的每個“兒子的子樹”。我們一次操作可以選擇兩個不同的子樹,然后把它們的總石子數各減少\(1\)(這樣可以保證選擇的兩個石子所在的節點不是“祖先-后代”關系)。目標是要讓每個兒子的總石子數都為\(0\)。
這就涉及到一個經典的模型。有\(n\)堆石子,每堆石子有\(a_i\)個。每次可以選擇兩個不同的堆,同時取走一枚石子。問能否通過若干次操作取完所有石子(即讓每堆的石子總數都變為\(0\))。設所有堆的石子總數為\(sum\),最大的那一堆的石子數為\(max\)。
- 當\(max>sum-max\)時,顯然無論怎么操作都無法取完所有石子。因為最大的那一堆一定會剩下\(max-(sum-max)\)個石子。
- 當\(max\leq sum-max\)時,有方法可以取完所有石子(或者在\(sum\)為奇數時讓石子只剩\(1\)個),構造如下:把所有石子排成一排,同一堆內的石子放在連續的一段。我們把石子按如下方法兩兩配對:\((1,1+\lfloor\frac{sum}{2}\rfloor),(2,2+\lfloor\frac{sum}{2}\rfloor),\dots,(\lfloor\frac{sum}{2}\rfloor,\lfloor\frac{sum}{2}\rfloor+\lfloor\frac{sum}{2}\rfloor)\)。顯然,因為沒有一堆石子的數量超過\(\lfloor\frac{sum}{2}\rfloor\),所以每一對石子都來自不同的堆。
在本題中,取一個石子就相當於向根走一步,因此,在第二種情況下,一定能把所有石子都移動到根。
考慮第一種情況,此時\(max\)的子樹內,石子太多了,我們要讓它內部消化掉一些。於是我們可以遞歸考慮\(max\)這個節點的所有兒子的子樹。重復上面所描述的判斷。
具體地,我們記一個\(f(u)\)表示在\(u\)的子樹內,最多能進行多少次“把兩個棋子同時向上移”的操作,也即最多能消掉多少對石子。顯然,實際上我們可以選擇進行\([0,f(u)]\)之間的任意次操作。
在以一個節點\(u\)為根的子樹內,求出每個兒子的石子數。
- \(max>sum-max\)時,設最大的那個兒子為\(v\),則\(f(u)=sum-max+\min(f(v),2max-sum)\)。
- \(max\leq sum-max\)時,\(f(u)=\lfloor\frac{sum}{2}\rfloor\)。
如果根節點處的\(sum\)是偶數,且\(f(root)=\frac{sum}{2}\),則有解,否則以\(root\)為根時無解。有解時,答案對\(f(root)\)取\(\min\)。
這個DP是\(O(n)\)的。因為要枚舉根。故總時間復雜度\(O(n^2)\)。
參考代碼:
//problem:agc034_e
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=2000,INF=1e9;
int n,s[MAXN+5],num[MAXN+5],f[MAXN+5];
char str[MAXN+5];
vector<int>G[MAXN+5];
void dfs(int u,int fa){
int son=0;
s[u]=0;num[u]=(str[u]=='1');
for(int i=0;i<(int)G[u].size();++i){
int v=G[u][i];
if(v==fa)continue;
dfs(v,u);
num[u]+=num[v];
s[v]+=num[v];
s[u]+=s[v];
if(!son||s[v]>s[son]){
son=v;
}
}
if(!son){f[u]=0;return;}
if(s[son]<=s[u]-s[son]){
f[u]=s[u]/2;
}
else{
f[u]=s[u]-s[son]+min(f[son],(s[u]-(s[u]-s[son]))/2);
}
}
int main() {
scanf("%d%s",&n,str+1);
for(int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),G[u].pb(v),G[v].pb(u);
int ans=INF;
for(int rt=1;rt<=n;++rt){
dfs(rt,0);
if(s[rt]&1)continue;
if(f[rt]>=s[rt]/2){
ans=min(ans,s[rt]/2);
}
}
if(ans==INF)puts("-1");
else printf("%d\n",ans);
return 0;
}
CF908G New Year and Original Order
考慮每個數碼\(d(1\leq d\leq9)\)對答案的貢獻。
考慮朴素的數位DP。設\(dp[i][j][k][0/1]\)表示從高到低考慮了前\(i\)位,有\(j\)個等於\(d\)的數位,有\(k\)個大於\(d\)的數位,當前數是否\(=X\),這些條件下的方案數。轉移時枚舉下一位填什么數。DP的復雜度為\(O(n^3\cdot 10)\),考慮優化。
記\(c(d)\)為\(d\)對答案貢獻的系數,則\(c(d)\)形式上應該是\(\sum10^i\)。則\(ans=\sum_{d=1}^{9}d\cdot c(d)=\sum_{d=1}^{9}\sum_{i=d}^{9}c(i)\)。這是因為考慮每個\(c(i)\)會被計算\(i\)次,即在\(\leq i\)的每個\(d\)的位置都被計算到一次。
考慮枚舉\(d\),求\(\sum_{i=d}^{9}c(i)\)。這相當於求所有\(\geq d\)的數位的\(10^i\)之和。而按題目要求把數位排序后,\(\geq d\)的數位一定是從最低位開始的連續的一段。於是我們只要記錄\(\geq d\)的數位有多少個即可。設\(dp[i][j][0/1]\)表示考慮了前\(i\)位,\(\geq d\)的數位有\(j\)個的方案數。DP的復雜度變為\(O(n^2\cdot 10)\)。因為要枚舉\(d\),故總時間復雜度\(O(n^2\cdot 10\cdot 9)\)。
參考代碼:
//problem:CF908G
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=700,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
char s[MAXN+5];
int n,dp[MAXN+5][MAXN+5][2];
int solve(int d){
memset(dp,0,sizeof(dp));
dp[0][0][1]=1;
for(int i=0;i<n;++i){
// dp[i] -> dp[i+1]
for(int j=0;j<=i;++j){
for(int t=0;t<=1;++t)if(dp[i][j][t]){
for(int x=0;x<=(t?s[i+1]-'0':9);++x){
add(dp[i+1][j+(x>=d)][t&&(x==(s[i+1]-'0'))],dp[i][j][t]);
}
}
}
}
int cur=1,sum=0,res=0;
for(int j=1;j<=n;++j){
add(sum,cur);
add(res,(ll)sum*mod1(dp[n][j][0]+dp[n][j][1])%MOD);
cur=10LL*cur%MOD;
}
return res;
}
int main() {
scanf("%s",s+1);n=strlen(s+1);
int ans=0;
for(int i=1;i<=9;++i){
add(ans,solve(i));
}
printf("%d\n",ans);
return 0;
}
agc024_f Simple Subsequence Problem
因為所有可能的答案只有\(2^{n+1}-1\)種,故考慮求出每個長度\(\leq n\)的01串分別是多少個給定串的子序列。
考慮識別一個串\(A\)是不是另一個串\(B\)的子序列,我們可以做一個簡單的貪心。維護一個指向\(B\)的下標的“指針”\(p\),初始時是\(0\)。對於\(A\)的每一位,把\(p\)移到\(p\)后面第一個等於\(A\)的這一位的位置。
考慮用一個自動機來描述這個貪心的過程。自動機的一個節點\((S,T)\)表示當前已經匹配好的\(A\)的前綴是\(S\),剩下的部分是\(T\)的一個子序列。當接下來要匹配\(A\)的某一位時,如果這一位是1
,則轉移到\((S+'1',T_1)\),否則轉移到\((S+'0',T_0)\)。其中\(T_c\)表示串\(T\)的第一個\(c\)后面的部分。如\(T=\)00110
時,\(T_1=\)10
,\(T_0=\)0110
。
這條路徑的起點為\((\emptyset,B)\),終點為\((A,\emptyset)\)(\(\emptyset\)表示空串)。
我們以所有題目給出的集合中的串為起點,就可以得到一張圖。因為在走的過程中\(S\)長度嚴格遞增,\(T\)長度嚴格遞減,所以一定不會走出環。因此這個圖是一個DAG。
又因為我們匹配的過程是基於“每次走到接下來第一個能匹配的位置”的貪心,因此從某個起點出發,識別一個串\(A\)時走的路徑是唯一的。也即從每個起點到點\((A,\emptyset)\),要么沒有路徑,要么有一條唯一的路徑。
因此,我們可以在DAG上做DP。求出到每個終點的路徑數,就是這個串是多少個起點的子序列。
設\(dp(S,T)\)表示走到DAG上\((S,T)\)這個節點的方案數。對於每個題目給出的集合中的串\(s\),初始化\(dp(\emptyset,s)=1\)。在具體實現中,我們開一個二維數組,\(dp[i][j]\),其中\(i\)表示\(T\)這個串的長度,\(j\)是\(S+T\)這個串的二進制狀壓。因為\(S+T\)這個串可能有前導零,我們可以在最高位的下一位放一個\(1\),表示這里是最高位。
DP時先枚舉\(T\)的長度\(i\)(因為一定是從長到段轉移),再枚舉\(S+T\)的長度\(j(j\geq i)\),再枚舉\(S+T\)這個串\(k\)。則當前的狀態就是\(dp[i][k|(1<<j)]\)。轉移有三種:直接結束(走到節點\((S,\emptyset)\)),匹配到下一個\(1\),或匹配到下一個\(0\)。
因為總狀態數是\(O(2^nn)\)的。轉移時找下一位\(0/1\)的復雜度為\(O(n)\),故總時間復雜度\(O(2^nn^2)\)(常數小可過)。如果預處理每個長度\(\leq n\)的二進制串的下一個\(0/1\)在哪一位,則可以優化到\(O(2^nn)\)。
參考代碼:
//problem:agc024_f
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
/* ------ by:duyi ------ */ // myt天下第一
int n,K,dp[21][1<<21];
char s[(1<<20)+5];
int main() {
scanf("%d%d",&n,&K);
for(int i=0;i<=n;++i){
scanf("%s",s);
for(int j=0;j<(1<<i);++j)if(s[j]=='1'){
dp[i][j|(1<<i)]=1;
}
}
for(int i=n;i>=1;--i){//未確定部分的長度
for(int j=i;j<=n;++j){//整個串的長度
int b=(1<<j);
for(int k=0;k<b;++k){
int tmp=dp[i][k|b],p=-1,q=-1;if(!tmp)continue;
for(int t=i-1;t>=0;--t)if(((k>>t)&1)==1){p=t;break;}
for(int t=i-1;t>=0;--t)if(((k>>t)&1)==0){q=t;break;}
dp[0][(k+b)>>i]+=tmp;
if(p!=-1)dp[p][((((k>>i)<<1)|1)<<p)|(((1<<p)-1)&k)|(1<<(j-(i-p)+1))]+=tmp;
if(q!=-1)dp[q][(((k>>i)<<1)<<q)|(((1<<q)-1)&k)|(1<<(j-(i-q)+1))]+=tmp;
}
}
}
for(int i=n;i>=0;--i){
for(int j=0;j<(1<<i);++j){
if(dp[0][j|(1<<i)]>=K){
for(int k=i-1;k>=0;--k)if((j>>k)&1)putchar('1');else putchar('0');
puts("");
return 0;
}
}
}
assert(0);
}
nflsoj49 【清華集訓2017】某位歌姬的故事
對序列的每個位置\(i\),我們維護一個\(up_i\),表示這個位置最大能填幾。那么一個限制\((l,r,w)\),就相當於讓\(i\in[l,r]\)的每個\(up_i\)的值對\(w\)取\(\min\)。
當然,還可能有一些位置從始至終未被任何一個限制覆蓋到。記這樣的位置有\(cnt\)個,則我們最后把答案乘以\(A^{cnt}\)即可。
題目里的每個限制\((l,r,w)\),相當於如下兩條要求:
-
\(\forall i \in[l,r]\ a_i\leq w\)
-
\(\exist i\in[l,r]\ a_i=w\)
如果讓所有\(a_i\)做到\(a_i\leq up_i\),則第一條限制就已經滿足了。考慮如何滿足第二條限制。
考慮每個\(w\)。我們發現\(\exist i\in[l,r]\ a_i=w\)只能由\(up_i=w\)的\(i\)來實現。因為\(up_i<w\)的\(i\)顯然無法實現(否則與第一條要求矛盾);而\(up_i>w\)的\(i\)說明這個位置根本沒被\([l,r]\)覆蓋到。
於是,我們枚舉每個\(w\),把\(up_i=w\)的這些點單獨拿出來做DP。設\(dp[i][j]\)表示考慮了前\(i\)個\(up_i=w\)的位置,最后一個滿足\(a_i=w\)(取到了這個上界)的位置在\(j\)時的方案數。對於每個位置\(i\),我們可以維護一個\(L[i]\)表示它的上一個被覆蓋的位置最遠可以在哪。則從\(dp[i][j]\)轉移到\(dp[i+1]\)時,轉移有兩種:
- 若\(j\geq L[i+1]\),則可以從\(dp[i][j]\)轉移到\(dp[i+1][j]\)。
- 在任何情況下,我們都能從\(dp[i][j]\)轉移到\(dp[i+1][i+1]\)。
求\(L[i]\):我們先初始化所有\(L[i]\)為\(0\)。然后對於每個限制\((l,r,w)\),讓\(L[r]\)對\(l\)取\(\max\)即可。
將所有位置都離散化后,一次DP的時間復雜度為\(O(Q^2)\)。故總時間復雜度\(O(Q^3)\)。
參考代碼:
//problem:nflsoj49
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=500,INF=0x3f3f3f3f,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int len,n,A,a[MAXN*2+5],pos[MAXN*2+5],val[MAXN*2+5],cnt,cnt_w;
struct Limits{int l,r,m;}q[MAXN+5];
bool fail;
int solve(int w){
static int p[MAXN*2+5],L[MAXN*2+5],dp[MAXN*2+5][MAXN*2+5];
int cnt_p=0;
for(int i=1;i<cnt;++i)if(a[i]==w)p[++cnt_p]=i,L[cnt_p]=0;
for(int i=1;i<=n;++i)if(q[i].m==w){
int l=lob(p+1,p+cnt_p+1,q[i].l)-p,
r=lob(p+1,p+cnt_p+1,q[i].r)-p-1;
if(l>r){fail=1;return 0;}
L[r]=max(L[r],l);
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=cnt_p;++i){
int v1=pow_mod(w,pos[p[i]+1]-pos[p[i]]),v2=pow_mod(w-1,pos[p[i]+1]-pos[p[i]]);
for(int j=0;j<i;++j){
if(j>=L[i])add(dp[i][j],(ll)dp[i-1][j]*v2%MOD);
add(dp[i][i],(ll)dp[i-1][j]*mod2(v1-v2)%MOD);
}
}
int res=0;
for(int j=0;j<=cnt_p;++j)add(res,dp[cnt_p][j]);
return res;
}
int main() {
int Testcases=read();while(Testcases--){
len=read();n=read();A=read();
cnt=cnt_w=0;
memset(a,0x3f,sizeof(a));
for(int i=1;i<=n;++i){
q[i].l=read(),q[i].r=read()+1,q[i].m=read();
pos[++cnt]=q[i].l,pos[++cnt]=q[i].r,val[++cnt_w]=q[i].m;
}
pos[++cnt]=1;pos[++cnt]=len+1;
sort(pos+1,pos+cnt+1);
cnt=unique(pos+1,pos+cnt+1)-(pos+1);
sort(val+1,val+cnt_w+1);
cnt_w=unique(val+1,val+cnt_w+1)-(val+1);
for(int i=1;i<=n;++i){
q[i].l=lob(pos+1,pos+cnt+1,q[i].l)-pos;
q[i].r=lob(pos+1,pos+cnt+1,q[i].r)-pos;
for(int j=q[i].l;j<q[i].r;++j)a[j]=min(a[j],q[i].m);
}
fail=0;
int ans=1;
for(int i=1;i<=cnt_w;++i){
ans=(ll)ans*solve(val[i])%MOD;
if(fail)break;
}
if(fail){puts("0");continue;}
for(int i=1;i<cnt;++i){
if(a[i]==INF)ans=(ll)ans*pow_mod(A,pos[i+1]-pos[i])%MOD;
}
printf("%d\n",ans);
}
return 0;
}
Part2 笛卡爾樹DP
loj2688 「POI2015」洗車 Car washes
先把權值離散化。
考慮原序列的笛卡爾樹(以最小值為根)。笛卡爾樹的每個子樹對應原序列的一個區間。本題中,我們用一個區間來表示一棵子樹,在寫法上類似於區間DP。對於笛卡爾樹上一個子樹\([l,r]\),設它的最小值所在位置為\(p\)(也就是子樹\([l,r]\)的根節點為\(p\)),最小值為\(v\),則我們在當前子樹上統計所有完全包含在本區間內,且經過根節點的車產生的貢獻。即\(l\leq a_i\leq p\leq b_i\leq r\)的這些車。把這個貢獻記為\(cost(l,r,p,v)\)。
設\(dp[l][r][v]\)表示整個\([l,r]\)子樹內,最小值為\(v\)時,產生的貢獻的最大值。轉移時枚舉最小值的位置\(p\),則:
可以發現,\(cost(l,r,p,v)\)就等於滿足\(l\leq a_i\leq p\leq b_i\leq r\)且\(c_i\geq v\)的車的數量,乘以\(v\)。而這個數量可以做三維前綴和后\(O(1)\)查詢。我們求完每個子樹\(dp[l][r][\dots]\)后,預處理出DP數組關於最后一維的后綴最大值,就可以對每個 \(p\) \(O(1)\) 計算了。
時間復雜度\(O(n^3m)\)。
參考代碼:
//problem:loj2688
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=50,MAXM=4000;
int n,m,a[MAXM+5],b[MAXM+5],c[MAXM+5],val[MAXM+5],cnt,s[MAXN+5][MAXN+5][MAXM+5],f[MAXN+5][MAXN+5][MAXM+5];
pii g[MAXN+5][MAXN+5][MAXM+5];
inline int cost(int i,int p,int j,int v){
/*
i<=a<=p<=b<=j
c>=v
*/
//int res=0;for(int t=1;t<=m;++t)if(i<=a[t] && a[t]<=p && p<=b[t] && b[t]<=j && c[t]>=v)res++;return res;
return (s[p][j][cnt]-s[p][p-1][cnt]-s[i-1][j][cnt]+s[i-1][p-1][cnt])
-(s[p][j][v-1]-s[p][p-1][v-1]-s[i-1][j][v-1]+s[i-1][p-1][v-1]);
}
struct node{
int lv,rv,p;
node(){}
node(int _lv,int _rv,int _p){lv=_lv,rv=_rv,p=_p;}
}tr[MAXN+5][MAXN+5][MAXM+5];
int res[MAXN+5];
void get_ans(int l,int r,int v,int p){
//cout<<l<<" "<<r<<" "<<v<<" "<<p<<endl;
//assert(v);assert(p);
res[p]=v;
if(p!=l){
get_ans(l,p-1,tr[l][r][v].lv,tr[l][p-1][tr[l][r][v].lv].p);
}
if(p!=r){
get_ans(p+1,r,tr[l][r][v].rv,tr[p+1][r][tr[l][r][v].rv].p);
}
}
int main() {
//freopen("1.in","r",stdin);
n=read();m=read();
for(int i=1;i<=m;++i)a[i]=read(),b[i]=read(),c[i]=read(),val[i]=c[i];
sort(val+1,val+m+1);cnt=unique(val+1,val+m+1)-(val+1);
for(int i=1;i<=m;++i)c[i]=lob(val+1,val+cnt+1,c[i])-val,s[a[i]][b[i]][c[i]]++;//離散化
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i][j][k-1];
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i][j-1][k];
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i-1][j][k];//三維前綴和
for(int i=1;i<=n;++i){
for(int j=1;j<=cnt;++j)f[i][i][j]=cost(i,i,i,j)*val[j],tr[i][i][j].p=i;
for(int j=cnt;j>=1;--j)g[i][i][j]=max(g[i][i][j+1],mk(f[i][i][j],j));
}
for(int len=2;len<=n;++len){
for(int i=1;i+len-1<=n;++i){
int j=i+len-1;
for(int p=i;p<=j;++p){
for(int v=1;v<=cnt;++v){
int l=(p==i?0:g[i][p-1][v].fst);
int r=(p==j?0:g[p+1][j][v].fst);
if(l+r+cost(i,p,j,v)*val[v]>=f[i][j][v]){
f[i][j][v]=l+r+cost(i,p,j,v)*val[v];
int _l=(p==i?0:g[i][p-1][v].scd);
int _r=(p==j?0:g[p+1][j][v].scd);
tr[i][j][v]=node(_l,_r,p);
}
//f[i][j][v]=max(f[i][j][v],l+r+cost(i,p,j,v)*val[v]);
}
}
for(int v=cnt;v>=1;--v){
g[i][j][v]=max(g[i][j][v+1],mk(f[i][j][v],v));
}
}
}
int ans=0,v=0;
for(int i=1;i<=cnt;++i)if(f[1][n][i]>=ans)ans=f[1][n][i],v=i;
printf("%d\n",ans);
get_ans(1,n,v,tr[1][n][v].p);
for(int i=1;i<=n;++i)printf("%d ",val[res[i]]);puts("");
return 0;
}
bzoj2616 SPOJ PERIODNI
定義柱狀圖的一個子圖是柱狀圖橫坐標的一個區間,截掉了下面的一定高度得到的柱狀圖,要求子圖中每列高度至少為\(1\)。按定義,我們從完整的原圖開始遞歸,每次找出當前區間中高度的最小值,把高度最小的這些列去掉之后,分出若干個更小的區間,將它們的下面(等於最小列高度的部分)砍掉,繼續遞歸這些小子圖。遞歸的邊界是如果當前區間內所有列高度相同,則不再繼續遞歸。按此方法,顯然可以划分出\(O(n)\)個子圖,這些子圖之間形成了樹狀的結構。我們可以把這個結構理解為一種廣義的“笛卡爾樹”,雖然它並不是二叉樹。
注意到:原序列的一個區間、柱狀圖的一個子圖、樹上的一個節點這三個概念現在是等價的。
我們在這個樹形結構上DP。用區間\([l,r]\)來表示樹上的一個節點(也就是一個子圖)。則這個子圖可以被划分為下方的一個極大完整矩形,和上面的若干小子圖(也就是它的兒子)。設\(dp[l,r][k]\)表示考慮了\([l,r]\)這個子圖,在里面放置了\(k\)個車的方案數。
先遞歸所有兒子,顯然這些兒子子圖之間互不影響,所以把它們用背包合並起來。
然后考慮下方的極大完整矩形。假設這個矩形大小為\(x\times y\)。如果我們在所有兒子中一共放了\(k\)個車,則會有\(k\)列是不能再放的了。那么在下面再放\(i\)個車方案數就是\({x\choose i}{y-k\choose i}i!\),我們枚舉\(i,k\),用類似於卷積的方法暴力合並即可。
本題的關鍵思想是:先算上方小區間(兒子),再算下方大區間(父親),因為大區間里一定包括了小區間的所有列,這樣計算時直接減去這\(k\)個用過的列即可。而不用關心具體是那些列被用過了,這就大大提高了效率。
背包暴力合並是\(O(n^2)\)的,因為共有\(O(n)\)個子圖,故總時間復雜度\(O(n^3)\)。
參考代碼:
//problem:bzoj2616
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=500,MOD=1000000007,MAXH=1000000;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int fac[MAXH+5],invf[MAXH+5];
inline int comb(int n,int k){
if(n<k)return 0;
return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
}
int n,K,a[MAXN+5],dp[MAXN+5][MAXN+5],id[MAXN+5][MAXN+5],cnt,tmp[MAXN+5];
void solve(int l,int r,int h){
//cout<<l<<" "<<r<<endl;
int x;assert(!id[l][r]);x=id[l][r]=++cnt;
int mn=MAXH+1;
vector<int>pos;
for(int i=l;i<=r;++i){
if(a[i]<mn)mn=a[i],pos.clear();
if(a[i]==mn)pos.pb(i);
}
assert(mn>h);
if(pos.size()==r-l+1){
for(int i=0;i<=min(mn-h,r-l+1);++i)dp[x][i]=(ll)comb(mn-h,i)*comb(r-l+1,i)%MOD*fac[i]%MOD;
return;
}
pos.pb(r+1);
int lst=l-1;
dp[x][0]=1;
for(int i=0;i<(int)pos.size();++i){
if(pos[i]>lst+1){
solve(lst+1,pos[i]-1,mn);
int y=id[lst+1][pos[i]-1];
for(int j=0;j<=r-l+1;++j){
tmp[j]=0;
for(int k=0;k<=j;++k){
add(tmp[j],(ll)dp[x][k]*dp[y][j-k]%MOD);
}
}
for(int j=0;j<=r-l+1;++j)dp[x][j]=tmp[j];
}
lst=pos[i];
}
memset(tmp,0,sizeof(tmp));
for(int i=0;i<=min(mn-h,r-l+1);++i){
for(int j=0;j+i<=r-l+1;++j){
add(tmp[i+j],(ll)dp[x][j]*comb(r-l+1-j,i)%MOD*comb(mn-h,i)%MOD*fac[i]%MOD);
}
}
for(int i=0;i<=r-l+1;++i)dp[x][i]=tmp[i];
}
int main() {
fac[0]=1;
for(int i=1;i<=MAXH;++i)fac[i]=(ll)fac[i-1]*i%MOD;
invf[MAXH]=pow_mod(fac[MAXH],MOD-2);
for(int i=MAXH-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
n=read();K=read();if(K>n){cout<<0<<endl;return 0;}
for(int i=1;i<=n;++i)a[i]=read();
solve(1,n,0);
// while(1){
// int l=read(),r=read(),x=read();
// cout<<dp[id[l][r]][x]<<endl;
// }
cout<<dp[id[1][n]][K]<<endl;
return 0;
}
agc026_d Histogram Coloring
考慮一個完整矩形的情況。如果第一行是01交替出現的,則下一行的每個位置可以和第一行相同,也可以不同;否則每個位置必須與前一行不同。
和上一題類似地,我們定義柱狀圖的子圖。以及這些子圖間構成了樹形結構。這里不再贅述。
設\(dp1[H]\)表示子圖\(H\)的最下面一行按01間隔染色時整個子圖的合法染色方案數,\(dp2[H]\)表示子圖\(H\)最下面一行沒有限制時整張圖的合法染色方案數。設\(H\)下方的極大完整矩形寬度為\(x\)(也就是\(H\)里最低的一列高度為\(x\)),\(H\)中有\(w\)列高度為\(x\)(並列最低),將\(H\)從下面截取\(x\)的高度后得到若干個子圖(也就是\(H\)的兒子)為\(c_1,c_2,\dots,c_k\)。
則\(dp1[H]=2^x\prod_{i=1}^{k}dp1[c_i]\),因為第一行交替染色時圖中某一列可以任意染色,染好這一列后其他位置的顏色都可以確定。
\(dp2[H]=2^w\prod_{i=1}^{k}(dp1[c_i]+dp2[c_i])+(2^x-2)\prod_{i=1}^{k}dp1[c_i]\)。后半部分是欽定每一列都不交替染色,則一列有\((2^x-2)\)種染法,此時第一行必須01交替,因此截下的子圖\(c_i\)的第一行也是交替的(\(dp1\))。前半部分讓每一列都01交替,則要么0開頭,要么1開頭,有\(2\)種染法。因此不在\(c_i\)下方的部分方案數為\(2^w\)。如果\(c_i\)第一行01交替的話\(c_i\)正下方的矩形部分會有\(2\)種染色方法,所以要\(dp1[c_i]\)要被計算\(2\)次(其中有一次已經包含在\(dp2[ci]\)中了)。
時間復雜度\(O(n^2)\)。
參考代碼(寫的略顯繁瑣。但本題實現不難,讀者可以嘗試自己實現):
//problem:agc026_d
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=1005,MOD=1e9+7;
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,h[MAXN];
vector<int>vec,w[MAXN];
int solve1(int l,int r,int k,int low){
//cout<<"dp1 "<<l<<" "<<r<<" "<<k<<endl;
int mx=0,mn=2e9;
for(int i=l;i<=r;++i)mx=max(mx,h[i]),mn=min(mn,h[i]);
if(mn>k)return solve1(l,r,mn,low);
if(mx==k&&mx==mn)return pow_mod(2,vec[k]-low);
int ans=1,st=lob(w[k].begin(),w[k].end(),l)-w[k].begin(),ed=lob(w[k].begin(),w[k].end(),r)-w[k].begin();
ed=min(ed,(int)w[k].size()-1);
if(w[k][ed]>r)ed--;
int lst=l;
for(int i=st;i<=ed;++i){
assert(w[k][i]>=l&&w[k][i]<=r);
if(lst<=w[k][i]-1)ans=(ll)ans*solve1(lst,w[k][i]-1,k+1,vec[k])%MOD;
lst=w[k][i]+1;
}
if(lst<=r)ans=(ll)ans*solve1(lst,r,k+1,vec[k])%MOD;
ans=(ll)ans*pow_mod(2,vec[k]-low)%MOD;
return ans;
}
int solve2(int l,int r,int k,int low){
//cout<<l<<" "<<r<<" "<<k<<endl;
int mx=0,mn=2e9;
for(int i=l;i<=r;++i)mx=max(mx,h[i]),mn=min(mn,h[i]);
if(mn>k)return solve2(l,r,mn,low);
if(mx==k&&mx==mn)return (pow_mod(2,r-l+1)-2+pow_mod(2,vec[k]-low))%MOD;
int st=lob(w[k].begin(),w[k].end(),l)-w[k].begin(),ed=lob(w[k].begin(),w[k].end(),r)-w[k].begin();
ed=min(ed,(int)w[k].size()-1);
if(w[k][ed]>r)ed--;
vector<int>dp1,dp2;
int lst=l,t=0;
for(int i=st;i<=ed;++i){
assert(w[k][i]>=l&&w[k][i]<=r);
t++;
if(lst<=w[k][i]-1)dp1.pb(solve1(lst,w[k][i]-1,k+1,vec[k])),dp2.pb(solve2(lst,w[k][i]-1,k+1,vec[k]));
lst=w[k][i]+1;
}
if(lst<=r)dp1.pb(solve1(lst,r,k+1,vec[k])),dp2.pb(solve2(lst,r,k+1,vec[k]));
int ans1=1,ans2=1;
for(int i=0;i<(int)dp1.size();++i)ans1=(ll)ans1*(dp1[i]+dp2[i])%MOD,ans2=(ll)ans2*dp1[i]%MOD;
ans1=(ll)ans1*pow_mod(2,t)%MOD;
ans2=(ll)ans2*(pow_mod(2,vec[k]-low)-2)%MOD;
return (ans1+ans2)%MOD;
}
int main() {
// freopen("data.txt","r",stdin);
n=read();
for(int i=1;i<=n;++i)h[i]=read(),vec.pb(h[i]);
vec.pb(0);
sort(vec.begin(),vec.end());
vec.erase(unique(vec.begin(),vec.end()),vec.end());
for(int i=1;i<=n;++i)h[i]=lob(vec.begin(),vec.end(),h[i])-vec.begin(),w[h[i]].pb(i);
cout<<solve2(1,n,1,0)<<endl;
return 0;
}
loj2743 「JOI Open 2016」摩天大樓
考慮把值按從大到小的順序插入序列中。那么每插入一個元素,其所在的連續段就是以其為根的笛卡爾樹上的子樹。
把\(a\)序列按從大到小排序(現在 \(a_i \geq a_{i + 1}\))。我們把絕對值這個貢獻做差分,然后攤到每次加入的數上。比方說加入\(a_i\)后當前序列中共有\(j\)個連續段,則差分的貢獻就是\(2\cdot j\cdot(a_i - a_{i + 1})\),因為每個連續段兩側會有兩個必定\(\leq a_{i+1}\)的數。特別地,當有某個連續段位於最左邊或最右邊時,要特判。
於是可以設計DP,令\(dp[i][j][k][x\in\{0,1\}][y\in\{0,1\}]\)表示考慮了前\(i\)大的數,當前序列中共有\(j\)個連續段,當前的差分總貢獻是\(k\),序列的左、右端點有沒有被加入。根據這些信息可以計算出,被拉大差值的兩個相鄰元素的數目,也就是對 \(k\) 新增的差分貢獻。轉移時,考慮新加入的元素,分“單獨成為一段”、“緊貼着某個原來的段”、“合並了兩個原來的段”三種情況討論。
時間復雜度\(O(n^2L)\)。
參考代碼:
//problem:loj2743
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=100,MAXL=1000,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,L,a[MAXN+5],dp[MAXN+5][MAXN+5][MAXL+5][2][2];
int main() {
n=read();L=read();
for(int i=1;i<=n;++i)a[i]=read();
sort(a+1,a+n+1);reverse(a+1,a+n+1);
if(n==1){cout<<1<<endl;return 0;}
dp[0][0][0][0][0]=1;
for(int i=0;i<n;++i){
for(int j=0;j<=i;++j){
for(int k=0;k<=L;++k){
for(int tl=0;tl<=1;++tl){
for(int tr=0;tr<=1;++tr)if(dp[i][j][k][tl][tr]){
//把第i+1個數放進來
int v=dp[i][j][k][tl][tr];
//case1:單獨一段
if(k+((j+1)*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j+(!tl)+(!tr)+i+1<=n)
add(dp[i+1][j+1][k+((j+1)*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j-1+(!tl)+(!tr))%MOD);
if(!tl)if(k+((j+1)*2-1-tr)*(a[i+1]-a[i+2])<=L)if(j+(!tr)+i+1<=n)
add(dp[i+1][j+1][k+((j+1)*2-1-tr)*(a[i+1]-a[i+2])][1][tr],v);
if(!tr)if(k+((j+1)*2-tl-1)*(a[i+1]-a[i+2])<=L)if(j+(!tl)+i+1<=n)
add(dp[i+1][j+1][k+((j+1)*2-tl-1)*(a[i+1]-a[i+2])][tl][1],v);
//case2:緊貼着一段
if(j&&k+(j*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j-1+(!tl)+(!tr)+i+1<=n)
add(dp[i+1][j][k+(j*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j*2-tl-tr)%MOD);
if(!tl)if(j&&k+(j*2-1-tr)*(a[i+1]-a[i+2])<=L)if(j-1+(!tr)+i+1<=n)
add(dp[i+1][j][k+(j*2-1-tr)*(a[i+1]-a[i+2])][1][tr],v);
if(!tr)if(j&&k+(j*2-tl-1)*(a[i+1]-a[i+2])<=L)if(j-1+(!tl)+i+1<=n)
add(dp[i+1][j][k+(j*2-tl-1)*(a[i+1]-a[i+2])][tl][1],v);
//case3:合並兩段
if(j>=2&&k+((j-1)*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j-2+(!tl)+(!tr)+i+1<=n)
add(dp[i+1][j-1][k+((j-1)*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j-1)%MOD);
}
}
}
}
}
int ans=0;
for(int i=0;i<=L;++i)add(ans,dp[n][1][i][1][1]);
cout<<ans<<endl;
return 0;
}
loj3228 「USACO 2019.12 Platinum」Tree Depth
考慮一個經典模型:求\(1\dots n\)的恰有\(K\)個逆序對的排列數。通常有兩種做法(它們本質相同):
- 做法一:從小到大枚舉每個數,把當前數\(i\)插入排列中。此時比\(i\)小的所有數的相對位置關系已經確定,而\(i\)可以插到它們之間的任意位置。所以插入\(i\)新增的逆序對數可以為\([0,i-1]\)中任意整數。
- 做法二:從左到右考慮每個位置。對於當前考慮的位置\(i\),前\(i-1\)個位置上值的相對大小關系已經確定。而當前位置上的值在前\(i\)個值中的排名可以任意指定,故新增的逆序對數也是\([0,i-1]\)中任意整數。
本題中,題目定義的構造過程就是構造一個笛卡爾樹。容易知道,對於兩個位置\(i,j\),\(j\)是\(i\)在笛卡爾樹上的祖先,當且僅當\(\min_{k=\min(i,j)}^{\max(i,j)}\{p_k\}=p_j\)。
根據期望的線性性,我們枚舉\(i,j\),求出\(j\)是\(i\)的祖先的合法方案數后累加到\(i\)的答案中。
考慮上述經典模型的第二種做法,但不是從左到右插入,而把插入的過程分為兩部分。第一部分先從\(i\)開始,向\(j\)的方向,一直走到序列的某一端,依次決定每個位置上元素的相對大小關系;第二部分從\(i\)開始,向\(j\)的反方向,一直走到序列的某一端,依次確定每個位置上元素的相對大小關系。具體地:
- 當\(j<i\)時,我們先從\(i\)到\(1\);再從\(i+1\)到\(n\)。
- 當\(j>i\)時,我們先從\(i\)到\(n\);再從\(i-1\)到\(1\)。
這樣,每個新位置所新增的逆序對數是:\([0,0],[0,1],[0,2],\dots,[0,n-1]\),除了\(j\)這個位置比較特殊,它需要保證是\([\min(i,j),\max(i,j)]\)這段區間內最小的。因此當\(j<i\)時,位置\(j\)新增的逆序對數為\(0\);\(j>i\)時,位置\(j\)新增的逆序對數為\(j-i\)。
朴素地做一次這樣的DP,復雜度是\(O(n^2K)\)的。又因為要枚舉位置\(i,j\),總復雜度為\(O(n^4K)\)。
發現DP的轉移相當於是乘以了一個形如\(x^0+x^1+\dots+x^d\)的生成函數。因為生成函數的最高次項是\(O(K)\)的,且所有項系數都為\(1\),故乘法可以\(O(K)\)實現。(也可以理解為是前綴和優化DP)。此時DP的復雜度降為\(O(nK)\)。
我們可以先\(O(nK)\)預處理好所有生成函數的積。然后枚舉\(i,j\),用預處理好的積除以一個最高次項為\(j-i\)的生成函數,再乘以\(x^0\)或\(x^{j-i}\)。和乘法同理,這個除法也可以\(O(K)\)實現。又發現我們要考慮的只有\(j-i\)的差值,因此不用枚舉\(i,j\),只需要枚舉\(j-i\)的差即可。
時間復雜度\(O(nK)\)。
參考代碼:
//problem:loj3228
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=300;
int n,K,MOD;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int f[MAXN*MAXN+5],D,ans[MAXN+5];
void mul(int d){
//乘以 (x^0+x^1+...+x^{d-1})
for(int i=D;i<D+d-1;++i)f[i]=0;
D+=d-1;
static int s[MAXN*MAXN+5];
s[0]=f[0];
for(int i=1;i<D;++i)s[i]=mod1(s[i-1]+f[i]);
for(int i=0;i<D;++i)f[i]=mod2(s[i]-(i-(d-1)>0?s[i-(d-1)-1]:0));
}
void div(int d){
//除以 (x^0+x^1+...+x^{d-1})
static int g[MAXN*MAXN+5];
assert(D>=d);
for(int i=D-d,s=0;i>=0;--i){
if(i+d<=D-d)sub(s,g[i+d]);
g[i]=mod2(f[i+(d-1)]-s);
add(s,g[i]);
}
for(int i=D-(d-1);i<D;++i)f[i]=0;
D-=(d-1);
for(int i=0;i<D;++i)f[i]=g[i];
}
int main() {
scanf("%d%d%d",&n,&K,&MOD);
f[0]=1;D=1;
for(int i=2;i<=n;++i)mul(i);
//for(int i=0;i<D;++i)cout<<f[i]<<" ";cout<<endl;
for(int d=1;d<n;++d){//(i,j)的距離 abs(i-j)=d
div(d+1);
for(int i=d+1;i<=n;++i)add(ans[i],f[K]);
if(K>=d)for(int i=1;i+d<=n;++i)add(ans[i],f[K-d]);
mul(d+1);
}
for(int i=1;i<=n;++i)printf("%d ",mod1(ans[i]+f[K]));puts("");
return 0;
}
Part3 DP套DP
DP套DP是給定一個DP問題A,用另一個DP去計算一種可能的A的輸入,使得A的DP結果為x。
說白了就是,外層的DP的狀態是另一個DP的結果。
這樣的問題,往往需要深入挖掘內層DP的性質,有時候還要對狀態數有一個合理的估計甚至是大膽的猜想。
loj6274 數字
考慮這樣一個問題:給定兩個數字\(P,Q\),求是否存在\(x,y\),滿足\(L_x\leq x\leq R_x,L_y\leq y\leq R_y\),使得\(x\operatorname{OR}y=P,x\operatorname{AND}y=Q\)。
這個問題可以用數位DP解決,令\(f[i][a][b][c][d]\ (a,b,c,d\in\{0,1\})\)表示從高到低考慮到第\(i\)位,\(x\)是否大於\(L_x\),是否小於\(R_x\);\(y\)是否大於\(L_y\),是否小於\(R_y\)。轉移時枚舉\(x,y\)的第\(i\)位分別是什么即可。且因為我們只需要判斷\(x,y\)的存在性,所以\(f\)數組的取值為\(\{0,1\}\)。
回到原問題。我們令\(F[i][s]\)表示從高到低考慮到第\(i\)位,小\(f\)數組是\(s\)時,共有多少個\(Q\)能達到此狀態。至於我們怎么用一個整數\(s\)來描述小\(f\)數組,因為發現小\(f\)數組下標共\(2^4\)種,取值共\(2\)種,所以把每個下標對應的取值壓到一起就好了,共\(2^{2^4}=2^{16}\)種狀態。
轉移時枚舉\(Q\)的第\(i\)位是什么即可。
時間復雜度\(O(60\cdot2^{16}\cdot\text{一大堆常數})\)。
參考代碼:
//problem:loj6274
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
/* ------ by:duyi ------ */ // myt天下第一
ull P,Lx,Rx,Ly,Ry,dp[61][1<<16];
int state(int a,int b,int c,int d){
return a+(b<<1)+(c<<2)+(d<<3);
}
#define forbit(i) for(int i=0;i<=1;++i)
int main() {
cin>>P>>Lx>>Rx>>Ly>>Ry;
dp[60][1<<state(0,0,0,0)]=1;
for(int i=59;i>=0;--i)for(int s=0;s<(1<<16);++s)if(dp[i+1][s]){
int p=((P>>i)&1ull);
int lx=((Lx>>i)&1ull),rx=((Rx>>i)&1ull),ly=((Ly>>i)&1ull),ry=((Ry>>i)&1ull);
forbit(q){
int new_s=0;
forbit(a)forbit(b)forbit(c)forbit(d)if(s&(1<<state(a,b,c,d))){
forbit(x)forbit(y){
if((x|y)!=p||(x&y)!=q)continue;
if(!a&&x<lx)continue;
if(!b&&x>rx)continue;
if(!c&&y<ly)continue;
if(!d&&y>ry)continue;
new_s|=(1<<state(a||x>lx,b||x<rx,c||y>ly,d||y<ry));
}
}
dp[i][new_s]+=dp[i+1][s];
}
}
ull ans=0;
for(int s=1;s<(1<<16);++s)ans+=dp[0][s];
cout<<ans<<endl;
return 0;
}