DP 做题记录
里面有一些数据结构优化 DP 的题目(如 XI.),以及普通 DP,树形 DP(XVI. ~ XXII.),区间 DP(XXVII. ~ XXXIV.)等。
*I. P3643 [APIO2016]划艇
题意简述:给出序列
,求出有多少序列 满足 或 ,同时非 的部分单调递增。
直到做到这题我才意识到我的 DP 水平有多菜。
注意到值域过大,于是对区间进行离散化,设离散化后的端点分别为
接下来考虑 DP:设
错误思路:
边界条件就是
考虑在同一区间内合法的情况:不妨枚举最右端的不在区间
综上所述,更新后的转移方程应为
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=500+5;
const int mod=1e9+7;
ll n,cnt,a[N],b[N],p[N<<1];
ll g[N],iv[N];
int main(){
cin>>n,g[0]=1;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i],iv[i]=(i==1?1:-mod/i*iv[mod%i]%mod+mod);
p[++cnt]=a[i],p[++cnt]=b[i]+1;
} sort(p+1,p+cnt+1),cnt=unique(p+1,p+cnt+1)-p-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(p+1,p+cnt+1,a[i])-p;
b[i]=lower_bound(p+1,p+cnt+1,b[i]+1)-p;
} for(int i=1;i<cnt;i++){
ll len=p[i+1]-p[i];
for(int j=n;j;j--)
if(a[j]<=i&&i<b[j]){
ll f=0,m=0,C=1;
for(int k=j;k;k--){
if(a[k]<=i&&i<b[k])C=C*(m+len)%mod*iv[m+1]%mod,m++;
f=(f+g[k-1]*C)%mod;
} g[j]=(g[j]+f)%mod;
}
} ll ans=0;
for(int i=1;i<=n;i++)ans=(ans+g[i])%mod;
cout<<ans<<endl;
return 0;
}
II. P7091 数上的树
题解。
III. CF1156F Card Bag
题解。
*IV. CF1542E2 Abnormal Permutation Pairs (hard version)
题解。
*V. AT693 文字列
很 nb 的题目,没想出来。注意我们不关注字符的相对顺序,只关心有没有相邻的相同字符,因此考虑 DP:设
时间复杂度 可是我连第一步 DP 都没想出来。
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define mem(x,v) memset(x,v,sizeof(x))
#define mcpy(x,y) memcpy(x,y,sizeof(y))
const ll mod=1e9+7;
const int N=260+5;
ll c,sum,a[N],f[N][N],C[N][N];
int main(){
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
C[i][j]=(j==0||j==i?1:C[i-1][j-1]+C[i-1][j])%mod;
for(int i=0;i<26;i++)cin>>a[i];
f[0][max(0ll,a[0]-1)]=1,sum=a[0];
for(int i=1;i<26;i++){
if(!a[i])continue;
c++;
for(int j=0;j<=sum;j++)
for(int k=0;k<=a[i];k++)
for(int l=0;l<=k;l++)
if(l<=j){
int nw=j-l+(a[i]-k);
f[c][nw]=(f[c][nw]+f[c-1][j]*C[a[i]-1][k-1]%mod*C[j][l]%mod*C[sum+1-j][k-l])%mod;
}
sum+=a[i];
}
cout<<f[c][0]<<endl;
return 0;
}
*VI. AT3859 [AGC020E] Encoding Subsets
好题。首先考虑没有子集限制怎么做:单纯设一个状态好像不好转移,因此设
有一部分我理解了很久才明白:在计算
但是这么做对于本题就不行了,不过我们受到上述启发,可以用字符串作为 DP 的变量。注意到字符串压缩时,只要有一个字符串的某一位为
const int N=5e3+5;
const ll mod=998244353;
map <string,int> f,g;
int calcg(string s);
int calcf(string s){
if(s.size()==0)return 1;
if(s.size()==1)return s[0]-'0'+1;
if(f.find(s)!=f.end())return f[s];
int res=0;
for(int i=1;i<=s.size();i++){
string pre="",suf="";
for(int j=0;j<i;j++)pre+=s[j];
for(int j=i;j<s.size();j++)suf+=s[j];
res=(res+1ll*calcg(pre)*calcf(suf))%mod;
} return f[s]=res;
}
int calcg(string s){
if(s.size()==0)return 1;
if(s.size()==1)return s[0]-'0'+1;
if(g.find(s)!=g.end())return g[s];
int res=0;
for(int i=1;i<s.size();i++)
if(s.size()%i==0){
string nw="";
for(int j=0;j<i;j++)nw+="1";
for(int j=0;j<s.size();j++)nw[j%i]=((nw[j%i]-'0')&(s[j]-'0'))+'0';
res=(res+calcf(nw))%mod;
} return g[s]=res;
}
int main(){
string s; cin>>s,cout<<calcf(s)<<endl;
return 0;
}
*VII. P2470 [SCOI2007]压缩
双倍经验,不过略有区别:设
const int N=50+5;
const ll mod=998244353;
char s[N];
int f[N][N][2];
bool vf[N][N][2];
bool check(int l,int r){
if(r-l+1&1)return 0;
for(int i=l,j=l+r+1>>1;j<=r;i++,j++)
if(s[i]!=s[j])return 0;
return 1;
}
int cmin(int &x,int y){if(x>y)x=y;}
int F(int l,int r,int tp){
if(l>r)return 0;
if(l==r)return 1;
if(vf[l][r][tp])return f[l][r][tp];
vf[l][r][tp]=1,f[l][r][tp]=r-l+1;
if(tp==0){
if(check(l,r))cmin(f[l][r][0],F(l,l+r>>1,0)+1);
for(int i=l;i<r;i++)cmin(f[l][r][0],F(l,i,0)+r-i);
} else for(int i=l;i<r;i++)
cmin(f[l][r][1],min(F(l,i,0),F(l,i,1))+min(F(i+1,r,0),F(i+1,r,1))+1);
return f[l][r][tp];
}
int main(){
scanf("%s",s+1);
cout<<min(F(1,strlen(s+1),0),F(1,strlen(s+1),1))<<endl;
return 0;
}
VIII. P4302 [SCOI2003]字符串折叠
三倍经验。和 AT3859 一模一样了属于是。
const int N=100+5;
const ll mod=998244353;
char s[N];
int f[N][N][2];
bool vf[N][N][2];
int cmin(int &x,int y){if(x>y)x=y;}
int F(int l,int r,int tp){
if(l>=r)return r-l+1;
if(vf[l][r][tp])return f[l][r][tp];
vf[l][r][tp]=1,f[l][r][tp]=r-l+1;
if(tp==0)for(int i=l;i<=r;i++)cmin(f[l][r][0],F(l,i,1)+F(i+1,r,0));
else for(int i=1;i<r-l+1;i++)if((r-l+1)%i==0){
bool ok=1; for(int j=l+i;j<=r;j++)ok&=s[j]==s[j-i];
if(ok)cmin(f[l][r][1],F(l,l+i-1,0)+3+((r-l+1)/i>=10));
} return f[l][r][tp];
}
int main(){
scanf("%s",s+1),cout<<F(1,strlen(s+1),0)<<endl;
return 0;
}
*IX. CF67C Sequence of Balls
若没有 4 操作,有一个显然的 DP,不谈。
考虑 swap 操作究竟应该怎么用:设
正确性其实挺难说明的,结合
const int N=5e3+5;
int f[N][N],pres[N],pret[N];
int ti,td,tr,te;
string s,t;
int main(){
cin>>ti>>td>>tr>>te>>s>>t; memset(f,0x3f,sizeof(f));
for(int i=0;i<=s.size();i++,mem(pret,0,N)){
if(i>1)pres[s[i-2]]=i-1;
for(int j=0;j<=t.size();j++){
if(i==0){f[i][j]=j*ti; continue;}
if(j==0){f[i][j]=i*td; continue;}
f[i][j]=min(f[i-1][j]+td,f[i][j-1]+ti);
if(s[i-1]==t[j-1])f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+tr);
int x=pres[t[j-1]],y=pret[s[i-1]];
if(x&&y)f[i][j]=min(f[i][j],f[x-1][y-1]+td*(i-x-1)+ti*(j-y-1)+te);
pret[t[j-1]]=j;
}
}
cout<<f[s.size()][t.size()]<<endl;
return 0;
}
*X. NOIP2021 六校联考 0820 T3 /se
题意简述:将
条线段划分为不超过 个集合,求每个集合中所有线段的并的长度之和的最大值。
, 。时限 1s,空限 512MB。
首先,对于完全覆盖某一条线段
因此,找到所有不完全覆盖任何其它线段的所有线段,按右端点排序,显然左端点也是单调的,所以设计 DP
- Trick:实际上,对于很多线段 DP 题目,我们可以挖掘性质使得所有需要考虑的线段在右端点单调的情况下,左端点也单调。
#include <bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef long double ld;
typedef pair <int,int> pii;
typedef unsigned long long ull;
typedef pair <long long,long long> pll;
#define fi first
#define se second
#define gc getchar()
#define pb emplace_back
#define mem(x,v,n) memset(x,v,sizeof(x[0])*n)
#define cpy(x,y,n) memcpy(x,y,sizeof(x[0])*n)
const ld Pi=acos(-1);
const ll mod=998244353;
inline ll read(){
ll x=0; bool sign=0; char s=gc;
while(!isdigit(s))sign|=s=='-',s=gc;
while(isdigit(s))x=(x<<1)+(x<<3)+(s-'0'),s=gc;
return sign?-x:x;
}
const int N=5e3+5;
int n,k,c,d;
ll ans,t[N],tt[N],pre[N],f[N][N];
struct seg{
ll l,r;
bool operator < (const seg &v) const{
return r<v.r;
}
bool operator == (const seg &v) const{
return l==v.l&&r==v.r;
}
}tmp[N],s[N];
int main(){
freopen("se.in","r",stdin);
freopen("se.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>tmp[i].l>>tmp[i].r;
for(int i=1;i<=n;i++){
tt[i]=tmp[i].r-tmp[i].l;
bool lap=0;
for(int j=1;j<=n;j++)
if(tmp[i].l<=tmp[j].l&&tmp[j].r<=tmp[i].r)
if(tmp[i]==tmp[j]&&i<j||!(tmp[i]==tmp[j])){
lap=1; break;
}
if(lap)t[++d]=tmp[i].r-tmp[i].l;
else s[++c]=tmp[i];
}
sort(s+1,s+c+1),mem(f,N,0xcf);
f[0][0]=f[c][0]=0;
for(int i=1;i<=c;i++)
for(int j=1;j<=min(k,i);j++){
pre[j]=max(pre[j],f[i-1][j-1]+s[i].r);
f[i][j]=max(0ll,pre[j]-s[i].l);
}
sort(t+1,t+d+1),reverse(t+1,t+d+1);
sort(tt+1,tt+n+1),reverse(tt+1,tt+n+1);
for(int j=1;j<=min(k,c);j++){
ll sum=f[c][j];
for(int i=1;i<=d&&i+j<=k;i++)sum+=t[i];
ans=max(ans,sum);
}
ll sum=0;
for(int i=1;i<=k-1;i++)sum+=tt[i];
cout<<max(ans,sum)<<endl;
return 0;
}
*XI. NOIP2021 六校联考 0818 T2 看烟花
题意简述:给出
个字符串 ,求用这些字符串的所有子串拼出 的最小代价,与方案数 。使用一次 的子串代价为 ,方案不同当且仅当 中存在至少一个字符满足来自不同的字符串。
, , , 。**保证 随机生成****。时限 6s。空限 512MB。
设
const int N=1e5+5;
const int S=6e4+5;
const pii inf={2e9,0};
struct node{
int id,cst;
bool operator < (const node &v) const {
return cst<v.cst;
}
}d[N];
char s[205][S];
struct SAM{
char s[S];
int l,cnt,las,cost,p,nwlen;
int son[S][5],fa[S],len[S];
void ins(char s){
int it=s-'a',p=las,cur=++cnt;
las=cur,len[cur]=len[p]+1;
while(!son[p][it])son[p][it]=cur,p=fa[p];
if(!p)return fa[cur]=1,void();
int q=son[p][it];
if(len[p]+1==len[q])return fa[cur]=q,void();
int cl=++cnt;
fa[cl]=fa[q],fa[q]=fa[cur]=cl;
cpy(son[cl],son[q],5),len[cl]=len[p]+1;
while(son[p][it]==q)son[p][it]=cl,p=fa[p];
}
void build(){
cnt=las=1,l=strlen(s+1);
for(int i=1;i<=l;i++)ins(s[i]);
}
int jump(char s){
int it=s-'a';
while(p){
if(!son[p][it])p=fa[p],nwlen=len[p];
else return p=son[p][it],++nwlen;
} return p=1,0;
}
}tr[205];
char t[N];
int n,len;
pii c[N<<2];
void add(int x,pii y){x++; while(x)c[x]=c[x]+y,x-=x&-x;}
pii query(int x,int r){pii s=inf; x++,r++; while(x<=r)s=s+c[x],x+=x&-x; return s;}
int main(){
freopen("firework.in","r",stdin);
freopen("firework.out","w",stdout);
scanf("%s%d",t+1,&n),len=strlen(t+1);
for(int i=1;i<=n;i++)scanf("%d%s",&d[i].cst,s[i]+1),d[i].id=i;
sort(d+1,d+n+1);
for(int i=1;i<=n;i++){
tr[i].cost=d[i].cst;
cpy(tr[i].s,s[d[i].id],S);
}
for(int i=1;i<=n;i++)tr[i].build(),tr[i].p=1;
for(int i=0;i<=len+1;i++)c[i]=inf;
add(0,{0,1});
for(int i=1;i<=len;i++){
pii ans=inf;
int tmpmx=0,cur=0;
for(int j=1;j<=n;j++){
int mxl=tr[j].jump(t[i]);
if(mxl<tmpmx)continue;
pii res=query(i-mxl,i-1);
res.fi+=tr[j].cost;
ans=ans+res;
cur=max(cur,mxl);
if(tr[j].cost!=tr[j+1].cost)tmpmx=cur,cur=0;
}
if(i<len)add(i,ans);
else cout<<ans.fi<<" "<<ans.se<<endl;
}
return 0;
}
区间查询,单点修改,且询问区间端点单调,可以用 BIT 优化。优化完变成
pii c[N<<2];void add(int x,pii y){x++; while(x)c[x]=c[x]+y,x-=x&-x;}pii query(int x,int r){pii s=inf; x++,r++; while(x<=r)s=s+c[x],x+=x&-x; return s;}
官方解法中根据
*XII. P2605 [ZJOI2010]基站选址
设
直接计算是
不要忘记最后再来一轮
const int N=2e4+5;
const int inf=1e9;
int n,k,ans=inf,tot,d[N],c[N],s[N],w[N],f[N],g[N];
int val[N<<2],laz[N<<2];
void down(int x){
if(laz[x]){
val[x<<1]+=laz[x],val[x<<1|1]+=laz[x];
laz[x<<1]+=laz[x],laz[x<<1|1]+=laz[x],laz[x]=0;
}
}
void push(int x){
val[x]=min(val[x<<1],val[x<<1|1]);
}
void build(int l,int r,int x){
if(l==r)return val[x]=f[l],void();
int m=l+r>>1;
build(l,m,x<<1),build(m+1,r,x<<1|1);
push(x),laz[x]=0;
}
void modify(int l,int r,int ql,int qr,int x,int v){
if(ql>qr)return;
if(ql<=l&&r<=qr)return val[x]+=v,laz[x]+=v,void();
int m=l+r>>1; down(x);
if(ql<=m)modify(l,m,ql,qr,x<<1,v);
if(m<qr)modify(m+1,r,ql,qr,x<<1|1,v);
push(x);
}
int query(int l,int r,int ql,int qr,int x){
if(ql<=l&&r<=qr)return val[x];
int m=l+r>>1,ans=inf; down(x);
if(ql<=m)ans=query(l,m,ql,qr,x<<1);
if(m<qr)ans=min(ans,query(m+1,r,ql,qr,x<<1|1));
return ans;
}
int pre(int x){return lower_bound(d+0,d+n+1,x)-d-1;}
int main(){
cin>>n>>k,d[0]=-inf;
for(int i=2;i<=n;i++)cin>>d[i];
for(int i=1;i<=n;i++)cin>>c[i];
for(int i=1;i<=n;i++)cin>>s[i];
for(int i=1;i<=n;i++)cin>>w[i];
mem(f,0x3f,N),f[0]=0;
for(int j=1;j<=k;j++){
build(0,n,1);
priority_queue <pii,vector<pii>,greater<pii>> q;
for(int i=1;i<=n;i++){
while(!q.empty()){
pii t=q.top();
if(t.fi>=d[i])break; q.pop();
modify(0,n,0,pre(d[t.se]-s[t.se]),1,w[t.se]);
}
g[i]=query(0,n,0,i,1)+c[i];
q.push({d[i]+s[i],i});
} cpy(f,g,N);
}
priority_queue <pii> q;
for(int i=n;~i;i--){
while(!q.empty()){
pii t=q.top();
if(t.fi<=d[i])break;
tot+=w[t.se],q.pop();
} ans=min(ans,f[i]+tot);
q.push({d[i]-s[i],i});
} cout<<ans<<endl;
return 0;
}
XIII. P2657 [SCOI2009] windy 数
一个比较简单的数位 DP。从高位往低位计算,设
const int N=15;
int d[N],f[N][10][2];
int calc(int x){
if(!x)return 0;
int dig=0,tmp=x; mem(f,0,N);
while(tmp)d[++dig]=tmp%10,tmp/=10;
for(int i=1;i<=d[dig];i++)f[dig][i][i==d[dig]]=1;
for(int i=dig-1;i;i--)
for(int j=0;j<10;j++){
if(j)f[i][j][0]=1;
for(int k=0;k<10;k++){
if(abs(j-k)<2)continue;
f[i][j][0]+=f[i+1][k][0];
if(d[i]==j)f[i][j][1]+=f[i+1][k][1];
else if(j<d[i])f[i][j][0]+=f[i+1][k][1];
}
}
int sum=0;
for(int i=0;i<10;i++)sum+=f[1][i][0]+f[1][i][1];
return sum;
}
int main(){
int l,r; cin>>l>>r;
cout<<calc(r)-calc(l-1)<<endl;
return 0;
}
XIV. P4127 [AHOI2009]同类分布
枚举各位数字之和,设
时间复杂度
#include<bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define ld long double
#define ull unsigned long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb emplace_back
#define mem(x,v,s) memset(x,v,sizeof(x[0])*(s))
#define cpy(x,y,s) memcpy(x,y,sizeof(x[0])*(s))
const int N=20;
const int S=165;
ll a,b,ans,d[N],f[N][S][S][2];
ll calc(int sum,ll x){
if(!x)return 0;
ll dig=0,tmp=x; mem(f,0,N);
while(tmp)d[++dig]=tmp%10,tmp/=10;
for(int i=1;i<=d[dig];i++)f[dig][i][i%sum][i==d[dig]]++;
for(int i=dig-1;i;i--)
for(int j=0;j<=sum;j++)
for(int k=0;k<sum;k++)
for(int l=0;l<10;l++){
if(!j&&!k&&l)f[i][l][l%sum][0]++;
int c=j+l,s=(k*10+l)%sum;
f[i][c][s][0]+=f[i+1][j][k][0];
if(l==d[i])f[i][c][s][1]+=f[i+1][j][k][1];
else if(l<d[i])f[i][c][s][0]+=f[i+1][j][k][1];
}
return f[1][sum][0][0]+f[1][sum][0][1];
}
int main(){
cin>>a>>b;
for(int i=1;i<=162;i++)ans+=calc(i,b)-calc(i,a-1);
cout<<ans<<endl;
return 0;
}
XV. P4124 [CQOI2016]手机号码
设
#include<bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define ld long double
#define ull unsigned long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb emplace_back
#define mem(x,v,s) memset(x,v,sizeof(x[0])*(s))
#define cpy(x,y,s) memcpy(x,y,sizeof(x[0])*(s))
const int N=15;
// f_{i,j,k,l,m,n} 表示第 i 位,是否出现连续三个相邻数字,
// 是否出现 4 和 8,是否顶到上界,当前连续段长 m,当前位为 n 的数字个数
ll l,r,buc[N],d[N],f[N][2][4][2][3][10];
ll calc(ll x){
ll dig=0,tmp=x,ans=0; mem(f,0,N);
while(tmp)d[++dig]=tmp%10,tmp/=10;
f[dig+1][0][0][1][1][0]=1;
for(int i=dig+1;i>1;i--)
for(int j=0;j<2;j++)
for(int k=0;k<3;k++)
for(int m=0;m<3;m++)
for(int n=0;n<10;n++){
// if(f[i][j][k][0][m][n])cout<<i<<" "<<j<<" "<<k<<" "<<0<<" "<<m<<" "<<n<<" "<<f[i][j][k][0][m][n]<<endl;
// if(f[i][j][k][1][m][n])cout<<i<<" "<<j<<" "<<k<<" "<<1<<" "<<m<<" "<<n<<" "<<f[i][j][k][1][m][n]<<endl;
for(int u=0;u<10;u++){
int nwk=k|buc[u],nwm=(n==u?m+1:1),nwj=j|(nwm==3);
if(nwk==3||!u&&i==dig+1)continue;
if(nwm==3)nwm=1;
f[i-1][nwj][nwk][0][nwm][u]+=f[i][j][k][0][m][n];
if(u<d[i-1])f[i-1][nwj][nwk][0][nwm][u]+=f[i][j][k][1][m][n];
else if(u==d[i-1])f[i-1][nwj][nwk][1][nwm][u]+=f[i][j][k][1][m][n];
}
}
for(int i=0;i<3;i++)
for(int j=0;j<2;j++)
for(int k=0;k<3;k++)
for(int l=0;l<10;l++)
ans+=f[1][1][i][j][k][l];
return ans;
}
int main(){
buc[4]=1,buc[8]=2;
cin>>l>>r,cout<<calc(r)-(l>1e10?calc(l-1):0);
return 0;
}
*XVI. P4649 [IOI2007] training 训练路径
神仙题。
首先补集转化:在所有土路上设置障碍的代价 减去 不设置障碍的边的代价之和的最大值,前提是符合题意。
对于两端颜色不同的非树边可以直接去掉,因为必须选。否则固定一个根,令非树边
接下来就是神仙的设计 DP 环节:考虑到度数很小,可以状压。设
对于每个节点
- 若不选
,则 。 - 若选择
,则贡献为 加上 到 和 到 的路径上所有非端点 不考虑包含 或 为子节点的儿子的贡献,记为 。注意特判 或 的情况。
直接这样做的复杂度是
时间复杂度
const int N = 1e3 + 5;
const int M = 5e3 + 5;
const int S = 10;
int c, hd[N], nxt[N << 1], to[N << 1];
void add(int u, int v) {nxt[++c] = hd[u], hd[u] = c, to[c] = v;}
struct Edge {
int u, v, c;
} e[M];
int n, m, sum, edg, dep[N], fa[N][S];
int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
for(int i = S - 1; ~i; i--)
if(dep[fa[x][i]] >= dep[y])
x = fa[x][i];
if(x == y) return x;
for(int i = S - 1; ~i; i--)
if(fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
void dfs1(int id, int f){
fa[id][0] = f, dep[id] = dep[f] + 1;
for(int i = 1; i < S; i++) fa[id][i] = fa[fa[id][i - 1]][i - 1];
for(int i = hd[id]; i; i = nxt[i]) {
int it = to[i];
if(it == f) continue;
dfs1(it, id);
}
}
int sid[N], rev[N], f[N][1 << S];
vector <Edge> ex[N];
void dfs2(int id) {
for(int i = hd[id]; i; i = nxt[i]) {
int it = to[i];
if(it == fa[id][0]) continue;
dfs2(it);
}
int son = 0;
for(int i = hd[id]; i; i = nxt[i]) {
int it = to[i];
if(it == fa[id][0]) continue;
sid[it] = 1 << son, rev[son++] = it;
}
assert(sid[id] == 0);
for(int i = 0; i < 1 << son; i++)
for(int j = 0; j < son; j++)
if(!(i >> j & 1))
f[id][i] += f[rev[j]][0];
for(Edge it : ex[id]) {
int u = it.u, v = it.v, c = it.c;
if(u != id) {
c += f[u][0];
while(fa[u][0] != id)
c += f[fa[u][0]][sid[u]], u = fa[u][0];
}
if(v != id) {
c += f[v][0];
while(fa[v][0] != id)
c += f[fa[v][0]][sid[v]], v = fa[v][0];
}
for(int i = 0; i < 1 << son; i++)
if(!(i & sid[u]) && !(i & sid[v]))
f[id][i] = max(f[id][i],
f[id][i | sid[u] | sid[v]] + c);
}
}
int main(){
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int u, v, c; cin >> u >> v >> c;
if(c) e[++edg] = {u, v, c}, sum += c;
else add(u, v), add(v, u);
} dfs1(1, 0);
for(int i = 1; i <= m; i++)
if((dep[e[i].u] & 1) == (dep[e[i].v] & 1))
ex[LCA(e[i].u, e[i].v)].pb(e[i]);
dfs2(1), cout << sum - f[1][0] << endl;
return 0;
}
XVII. BZOJ 2164 采矿
题意简述:给定一棵树,有
个士兵,在节点 处放置 个士兵的贡献为 。两种操作:更改所有 或给出 ,保证 是 的 祖先(可能相等),求在 的子树中任意节点放置任意数量士兵,且在 的简单路径上(不包括 ,若 相等则包括 )最多一个节点放置任意数量士兵,同时士兵数量总和不超过 的最大贡献。 数据生成方法见输入格式。
, , 。
对
const int N = 2e4 + 5;
const int M = 50 + 5;
const int X = 1 << 16;
const int Y = (1ll << 31) - 1;
void cmax(int &x, int y) {if(x < y) x = y;}
int cnt, hd[N], nxt[N << 1], to[N << 1];
void add(int u, int v) {
nxt[++cnt] = hd[u], hd[u] = cnt;
to[cnt] = v;
}
int n, m, C, A, B, Q;
int GetInt() {
A = ((A xor B) + (B / X) + 1ll * B * X) & Y;
B = ((A xor B) + (A / X) + 1ll * A * X) & Y;
// cout << A << " " << B << " " << (A ^ B) << " " << Y << endl;
return (A xor B) % Q;
}
struct Knapsack {
int a[M];
void clear() {
mem(a, 0, M);
}
void read() {
for(int i = 1; i <= m; i++) a[i] = GetInt();
sort(a + 1, a + m + 1);
// cout << "check : " << endl;
// for(int i = 1; i <= m; i++) cout << a[i] << " "; cout << endl;
}
void Getmax(Knapsack x) {
for(int i = 1; i <= m; i++) cmax(a[i], x.a[i]);
}
void Merge(Knapsack x) {
static int f[M]; mem(f, 0, M);
for(int i = 0; i <= m; i++)
for(int j = 0; j <= m - i; j++)
cmax(f[i + j], a[i] + x.a[j]);
cpy(a, f, M);
}
} a[N], val[N << 2], mx[N << 2], ansv, ansm;
// Tree Partition
int dnum, dep[N], sz[N], fa[N], son[N], top[N], rev[N], dfn[N];
void dfs1(int id) {
dep[id] = dep[fa[id]] + 1, sz[id] = 1;
for(int i = hd[id]; i; i = nxt[i]) {
int it = to[i];
dfs1(it), sz[id] += sz[it];
if(sz[it] > sz[son[id]]) son[id] = it;
}
}
void dfs2(int id, int tp) {
top[id] = tp, dfn[id] = ++dnum, rev[dnum] = id;
if(son[id]) dfs2(son[id], tp);
for(int i = hd[id]; i; i = nxt[i]) {
int it = to[i];
if(it == son[id]) continue;
dfs2(it, it);
}
}
// SegTree
void push(int x) {
val[x] = val[x << 1], val[x].Merge(val[x << 1 | 1]);
mx[x] = mx[x << 1], mx[x].Getmax(mx[x << 1 | 1]);
}
void build(int l, int r, int x) {
if(l == r) return val[x] = mx[x] = a[rev[l]], void();
int m = l + r >> 1;
build(l, m, x << 1);
build(m + 1, r, x << 1 | 1);
push(x);
}
void modify(int l, int r, int p, int x) {
if(l == r) return val[x] = mx[x] = a[rev[p]], void();
int m = l + r >> 1;
if(p <= m) modify(l, m, p, x << 1);
else modify(m + 1, r, p, x << 1 | 1);
push(x);
}
void query1(int l, int r, int ql, int qr, int x) {
if(ql <= l && r <= qr) return ansv.Merge(val[x]), void();
int m = l + r >> 1;
if(ql <= m) query1(l, m, ql, qr, x << 1);
if(m < qr) query1(m + 1, r, ql, qr, x << 1 | 1);
}
void query2(int l, int r, int ql, int qr, int x) {
if(ql <= l && r <= qr) return ansm.Getmax(mx[x]), void();
int m = l + r >> 1;
if(ql <= m) query2(l, m, ql, qr, x << 1);
if(m < qr) query2(m + 1, r, ql, qr, x << 1 | 1);
}
int main() {
cin >> n >> m >> A >> B >> Q;
for(int i = 2; i <= n; i++) cin >> fa[i], add(fa[i], i);
dfs1(1), dfs2(1, 1);
for(int i = 1; i <= n; i++) a[i].read();
cin >> C, build(1, n, 1);
for(int i = 1; i <= C; i++) {
int op, u, v; cin >> op >> u;
if(op == 0) {
a[u].read(), modify(1, n, dfn[u], 1);
continue;
} cin >> v;
ansv.clear(), ansm.clear();
query1(1, n, dfn[u], dfn[u] + sz[u] - 1, 1);
if(u == v) {
printf("%d\n", ansv.a[m]);
continue;
} u = fa[u];
while(top[u] != top[v]) {
query2(1, n, dfn[top[u]], dfn[u], 1);
u = fa[top[u]];
} query2(1, n, dfn[v], dfn[u], 1);
ansv.Merge(ansm), printf("%d\n", ansv.a[m]);
}
return 0;
}
*XVIII. P3262 [JLOI2015]战争调度
注意到对于一个子节点,我们无法确定它的贡献,因为我们不知道它的父亲的状态。但是由于是完全二叉树,我们可以爆搜:对于叶子结点,它的时间复杂度为
故时间复杂度为
const int N = 10;
void cmax(int &x, int y) {if(y > x) x = y;}
int n, m, g[1 << N][1 << N];
int type[N], w[1 << N][N], f[1 << N][N];
// type = 1 : war
void dfs(int id, int dep) {
if(dep == 0) {
g[id][0] = g[id][1] = 0;
for(int i = 1; i < n; i++)
if(type[id >> i] == 1) g[id][1] += w[id][i];
else g[id][0] += f[id][i];
return;
}
type[id] = 1, dfs(id << 1, --dep), dfs(id << 1 | 1, dep);
int sz = 1 << dep;
for(int i = 0; i <= sz << 1; i++) g[id][i] = 0;
for(int i = 0; i <= sz; i++)
for(int j = 0; j <= sz; j++)
cmax(g[id][i + j], g[id << 1][i] + g[id << 1 | 1][j]);
type[id] = 2, dfs(id << 1, dep), dfs(id << 1 | 1, dep);
for(int i = 0; i <= sz; i++)
for(int j = 0; j <= sz; j++)
cmax(g[id][i + j], g[id << 1][i] + g[id << 1 | 1][j]);
}
int main(){
cin >> n >> m;
for(int i = 1 << n - 1; i < 1 << n; i++)
for(int j = 1; j < n; j++)
cin >> w[i][j];
for(int i = 1 << n - 1; i < 1 << n; i++)
for(int j = 1; j < n; j++)
cin >> f[i][j];
dfs(1, n - 1);
int ans = 0;
for(int i = 0; i <= m; i++) cmax(ans, g[1][i]);
cout << ans << endl;
return 0;
}
*XIX. SP3734 PERIODNI - Periodni
首先,显然的是一列最多放一个数。我们设
根据树形背包的复杂度分析,枚举
const int N = 500 + 5;
const int H = 1e6 + 5;
const ll mod = 1e9 + 7;
void add(ll &x, ll y) {x = (x + y) % mod;}
ll ksm(ll a, ll b) {
ll s = 1;
while(b) {
if(b & 1) s = s * a % mod;
a = a * a % mod, b >>= 1;
} return s;
}
ll fc[H], ifc[H];
ll A(int n, int m) {return fc[n] * ifc[n - m] % mod;}
ll C(int n, int m) {return fc[n] * ifc[m] % mod * ifc[n - m] % mod;}
ll n, k, ans, h[N], f[N][N], sz[N];
int stc[N], top;
vector <int> id;
// merge x to y
void merge(int x, int y) {
static ll g[N]; mem(g, 0, N);
ll dif = h[x] - h[y];
for(int i = 0; i < sz[x]; i++)
for(int k = 0; k <= min(sz[x] - i, dif); k++)
for(int j = 0; j < sz[y]; j++)
add(g[i + j + k], f[x][i] * f[y][j] % mod *
C(sz[x] - i, k) % mod * A(dif, k));
cpy(f[y], g, N), sz[y] += sz[x];
}
int main(){
cin >> n >> k, fc[0] = ifc[0] = 1;
for(int i = 1; i < H; i++) fc[i] = fc[i - 1] * i % mod;
ifc[H - 1] = ksm(fc[H - 1], mod - 2);
for(int i = H - 2; i; i--) ifc[i] = ifc[i + 1] * (i + 1) % mod;
for(int i = 1; i <= n; i++)
id.pb(i), sz[i] = f[i][0] = 1, cin >> h[i];
while(id.size() > 1) {
ll mxh = 0, p = -1, mer;
for(int it : id) mxh = max(mxh, h[it]);
for(int i = 0; i < id.size(); i++)
if(h[id[i]] == mxh) p = i;
if(p == 0) mer = 1;
else if(p == id.size() - 1) mer = id.size() - 2;
else if(h[id[p - 1]] > h[id[p + 1]]) mer = p - 1;
else mer = p + 1;
merge(id[p], id[mer]), id.erase(id.begin() + p);
}
int res = id[0];
for(int i = 0; i <= k; i++) if(h[res] >= k - i)
add(ans, f[res][i] * C(n - i, k - i) % mod * A(h[res], k - i));
cout << ans << endl;
return 0;
}
XX. P6419 [COCI2014-2015#1] Kamp
注意到对于一个点
每次向下传
前者线性,后者线性对数。
需要注意子节点的子树关键点的个数为
const int N=5e5+5;
ll cnt,hd[N<<1],nxt[N<<1],to[N<<1],val[N<<1];
void add(int u,int v,int w){nxt[++cnt]=hd[u],hd[u]=cnt,to[cnt]=v,val[cnt]=w;}
ll n,k,buc[N],sz[N];
ll sumd[N],mxd[N],ans[N];
void dfs1(int id,int f){
sz[id]+=buc[id];
for(int i=hd[id];i;i=nxt[i]){
int it=to[i];
if(it==f)continue;
dfs1(it,id),sz[id]+=sz[it];
if(sz[it])
mxd[id]=max(mxd[id],mxd[it]+val[i]),sumd[id]+=sumd[it]+val[i];
}
}
struct check{
ll dist,id;
bool operator < (const check &v) const {
return dist>v.dist;
}
};
void dfs2(int id,int f){
vector <check> c; int deg=0;
for(int i=hd[id];i;i=nxt[i])deg++;
c.resize(deg); int p=0;
for(int i=hd[id];i;i=nxt[i])
if(sz[id]!=k||to[i]!=f)c[p++]={mxd[to[i]]+val[i],to[i]};
sort(c.begin(),c.begin()+deg);
ans[id]=sumd[id]*2-c[0].dist;
for(int i=hd[id];i;i=nxt[i]){
int it=to[i];
if(it==f)continue;
sumd[it]=sumd[id]+(sz[it]==0?val[i]:sz[it]==k?-val[i]:0);
ll tmp=mxd[id];
if(it==c[0].id)mxd[id]=c[1].dist;
else mxd[id]=c[0].dist;
dfs2(it,id);
mxd[id]=tmp;
}
}
int main(){
cin>>n>>k;
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
for(int i=1;i<=k;i++)buc[read()]=1;
dfs1(1,0),dfs2(1,0);
for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
cout<<endl;
return 0;
}
*XXI. P3757 [CQOI2017]老C的键盘
设
若
否则将
树形背包再枚举一维,时间复杂度
const int N=100+5;
const ll mod=1e9+7;
int cnt,hd[N<<1],nxt[N<<1],to[N<<1];
char val[N<<1];
void add(int u,int v,char w){
nxt[++cnt]=hd[u],hd[u]=cnt,to[cnt]=v,val[cnt]=w;
}
ll n,sz[N],f[N][N],c[N][N],g[N]; // f i j 表示以 i 为根有 j 个数小于 h_i
void add(ll &x,ll y){x=(x+y)%mod;}
void dfs(int id){
sz[id]=1,f[id][1]=1;
for(int t=hd[id];t;t=nxt[t]){
int it=to[t],lim; dfs(it),lim=sz[id]+sz[it];
static ll g[N]; mem(g,0,N);
for(int i=sz[id];i;i--)
for(int j=sz[it];j;j--){
ll coef=f[id][i]*f[it][j]%mod;
if(val[t]=='>') for(int k=lim;k>=i+j;k--)
add(g[k],coef*c[k-1][i-1]%mod*c[lim-k][sz[id]-i]);
else for(int k=i+j-1;k>=i;k--)
add(g[k],coef*c[k-1][i-1]%mod*c[lim-k][sz[id]-i]);
}
cpy(f[id],g,N),sz[id]=lim;
}
}
int main(){
cin>>n;
for(int i=0;i<=n;i++)
for(int j=0;j<=i;j++)
c[i][j]=(j==0||j==i?1:(c[i-1][j-1]+c[i-1][j]))%mod;
for(int i=2;i<=n;i++){
char s; cin>>s;
add(i/2,i,s);
} dfs(1);
ll ans=0;
for(int i=1;i<=n;i++)add(ans,f[1][i]);
cout<<ans<<endl;
return 0;
}
*XXII. P4099 [HEOI2013]SAO
XXI. 的究极加强版,注意到后面的组合数与
需要特别注意
const int N=1000+5;
const ll mod=1e9+7;
int cnt,hd[N<<1],nxt[N<<1],to[N<<1];
char val[N<<1];
void add(int u,int v,char w){
nxt[++cnt]=hd[u],hd[u]=cnt,to[cnt]=v,val[cnt]=w;
}
ll n,sz[N],f[N][N],c[N][N]; // f i j 表示以 i 为根有 j 个数小于 h_i
void add(ll &x,ll y){x=(x+y)%mod;}
void dfs(int id,int fa){
sz[id]=1,f[id][1]=1;
for(int t=hd[id];t;t=nxt[t]){
int it=to[t],lim;
if(it==fa)continue;
dfs(it,id),lim=sz[id]+sz[it];
static ll g[N],pre[N]; mem(g,0,N),mem(pre,0,N);
for(int i=1;i<=sz[it];i++)pre[i]=(pre[i-1]+f[it][i])%mod;
for(int i=sz[id];i;i--){
if(val[t]=='>')
for(int k=i;k<i+sz[it];k++)
add(g[k],c[k-1][i-1]*c[lim-k][sz[id]-i]%mod*
f[id][i]%mod*(pre[sz[it]]-pre[k-i]+mod));
else
for(int k=i+1;k<=i+sz[it];k++)
add(g[k],c[k-1][i-1]*c[lim-k][sz[id]-i]%mod*
f[id][i]%mod*pre[k-i]);
}
cpy(f[id],g,N),sz[id]=lim;
}
}
void solve(){
cin>>n,mem(f,0,N),cnt=0,mem(hd,0,N);
for(int i=2;i<=n;i++){
int x,y; char s; cin>>x>>s>>y,x++,y++;
add(x,y,s),add(y,x,s=='>'?'<':'>');
} dfs(1,0);
ll ans=0;
for(int i=1;i<=n;i++)add(ans,f[1][i]);
cout<<ans<<endl;
}
int main(){
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
c[i][j]=(j==0||j==i?1:(c[i-1][j-1]+c[i-1][j]))%mod;
int T; cin>>T;
while(T--)solve();
return 0;
}
*XXIII. CF1327F AND Segments
怎么这么像 NOI 2020 D1T2。
好题。首先题目对于每一位是独立的,那么分别考虑所有限制。可以预处理出
- 若
则 。 - 若
则 ,这表示位置 选 。 - 若
,当 必须填 时 ,否则为 。
注意到
const int N = 5e5 + 5;
const int mod = 998244353;
struct Limit {
int l, r, x;
} c[N];
int n, k, m, ans = 1;
int f[N], pos[N], buc[N];
int main() {
cin >> n >> k >> m;
for(int i = 1; i <= m; i++)
scanf("%d %d %d", &c[i].l, &c[i].r, &c[i].x);
for(int i = 0, s; i < k; i++) {
mem(f, 0, N), mem(pos, 0, N), mem(buc, 0, N), f[0] = s = 1;
for(int j = 1; j <= m; j++)
if(c[j].x >> i & 1) buc[c[j].l]++, buc[c[j].r + 1]--;
else pos[c[j].r + 1] = max(pos[c[j].r + 1], c[j].l);
for(int j = 1; j <= n; j++) buc[j] += buc[j - 1];
for(int j = 1; j <= n; j++) {
pos[j] = max(pos[j], pos[j - 1]);
for(int k = pos[j - 1]; k < pos[j]; k++) s = (s + mod - f[k]) % mod;
if(!buc[j]) f[j] = s; s = (f[j] + s) % mod;
} for(int k = pos[n]; k < max(pos[n], pos[n + 1]); k++) s = (s + mod - f[k]) % mod;
ans = 1ll * ans * s % mod;
} cout << ans << endl;
return 0;
}
*XXIV. 2020 联考模拟知临 食堂计划
题意简述:给出一张带边权的图,求有多少条
的最短路无序对 满足不存在 使得 。
, 。
首先求出所有可能在最短路上的边(有向),设它们的边导出子图为
DAG 上计数应考虑容斥,设
其中
最后若
求
const int N = 2e3 + 5;
const int M = 3e4 + 5;
const int mod = 1e9 + 9;
void add(int &x, int y) { x = (x + y) % mod; }
int e[N][N], u[M], v[M], w[M];
int cnt, hd[N], nxt[M << 1], to[M << 1], val[M << 1];
void add(int u, int v, int w) {
nxt[++cnt] = hd[u], hd[u] = cnt;
to[cnt] = v, val[cnt] = w;
}
int n, m, S, T, L, vis[N], disS[N], disT[N], pa[N];
void Dijkstra(int S, int *dis, int *p) {
priority_queue<pii, vector<pii>, greater<pii>> q;
mem(dis, 63, N), q.push({ dis[S] = 0, S });
int cnt = 0;
while (!q.empty()) {
pii t = q.top();
q.pop();
int id = t.se, d = t.fi;
if (vis[id])
continue;
vis[id] = 1, p[++cnt] = id;
for (int i = hd[id]; i; i = nxt[i]) {
int it = to[i], v = d + val[i];
if (dis[it] > v)
q.push({ dis[it] = v, it });
}
}
mem(vis, 0, N);
}
int p[N][N], ans[N], have[N];
int main() {
freopen("dining.in", "r", stdin);
freopen("dining.out", "w", stdout);
cin >> n >> m >> S >> T, mem(e, 63, N);
for (int i = 1; i <= m; i++) {
u[i] = read(), v[i] = read(), w[i] = read();
e[u[i]][v[i]] = e[v[i]][u[i]] = min(e[u[i]][v[i]], w[i]);
}
for (int i = 1; i <= m; i++)
if (e[u[i]][v[i]] == w[i])
e[u[i]][v[i]] = mod, add(u[i], v[i], w[i]), add(v[i], u[i], w[i]);
// Dijkstra & BFS
Dijkstra(T, disT, pa), Dijkstra(S, disS, pa);
assert(disT[S] == disS[T]), L = disT[S];
int cnt = 0;
for (int i = 1; i <= n; i++) {
p[i][i] = 1;
static int buc[N]; mem(buc, 0, N);
for (int j = 1; j <= n; j++) {
int id = pa[j]; buc[id] = 1;
if(!p[i][id]) continue;
for (int k = hd[id]; k; k = nxt[k]) {
int it = to[k];
if (disS[id] + disT[it] + val[k] == L)
p[i][it] = (p[i][it] + p[i][id]) % mod;
}
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (u[j] == S && v[j] == i || u[j] == i && v[j] == S) {
if (w[j] == disS[i]) {
have[i] = 1;
break;
}
}
// Dynamic Programming for answer
for (int i = 2; i <= n; i++) {
int id = pa[i];
if (disS[id] + disT[id] != L)
continue;
ans[id] = 1ll * p[S][id] * (p[S][id] - 1) / 2 % mod;
for (int j = 2; j < i; j++) {
int it = pa[j];
add(ans[id], mod - 1ll * p[it][id] * p[it][id] % mod * ans[it] % mod);
if (have[it])
add(ans[id], mod - 1ll * p[it][id] * (p[it][id] - 1) / 2 % mod);
}
}
cout << (ans[T] + have[T]) % mod << endl;
return 0;
}
*XXV. P2051 [AHOI2009]中国象棋
乍一眼看起来很难,因此要挖掘性质:不妨一行一行地放,不难发现每一列是等价的。因此设
- 不摆象棋:
。 - 在空列摆一个象棋:
。 - 在一个象棋的列摆一个象棋:
。 - 在空列摆两个象棋:
。 - 在空列和一个象棋的列各摆一个象棋:
。 - 在一个象棋的列摆两个象棋:
。
时间复杂度
const int N = 100 + 5;
const ll mod = 9999973;
int n, m, ans, f[N][N];
void add(int &x, ll y) {x = (x + y) % mod;}
int b(int x) {return x * (x - 1) / 2;}
int main() {
cin >> n >> m, f[0][0] = 1;
for(int i = 1; i <= n; i++) {
static int g[N][N];
mem(g, 0, N);
for(int j = 0; j <= m; j++)
for(int k = 0; k <= m - j; k++)
if(f[j][k]) {
add(g[j][k], f[j][k]);
if(j + k + 1 <= m) add(g[j + 1][k], 1ll * f[j][k] * (m - j - k));
if(j) add(g[j - 1][k + 1], 1ll * f[j][k] * j);
if(j + k + 2 <= m) add(g[j + 2][k], 1ll * f[j][k] * b(m - j - k));
if(j) add(g[j][k + 1], 1ll * f[j][k] * (m - j - k) * j);
if(j >= 2) add(g[j - 2][k + 2], 1ll * f[j][k] * b(j));
} cpy(f, g, N);
} for(int i = 0; i <= m; i++)
for(int j = 0; j <= m; j++)
add(ans, f[i][j]);
cout << ans << endl;
return 0;
}
*XXVI. P3147 [USACO16OPEN]262144
很有趣的题。注意到数的值域只有
const int N = 1 << 18;
int n, ans, f[N][60];
int main() {
cin >> n;
for(int i = 1; i <= n; i++) f[i][read()] = i + 1;
for(int i = 1; i <= 58; i++)
for(int j = 1; j <= n; j++) {
if(!f[j][i]) f[j][i] = f[f[j][i - 1]][i - 1];
if(f[j][i]) ans = i;
} cout << ans << endl;
return 0;
}
*XXVII. P5336 [THUSC2016]成绩单
神仙区间 DP。
一开始想假了,设
重要思想:注意到人数很少,所以不妨先对成绩大小进行离散化,然后将其也设计在 DP 方程中。
那么设
初始值
为什么没有想到解法:没有接触过值域设计在 DP 方程中的题目,以后一定要注意。
const int N = 50 + 5;
const int inf = 1e9;
int n, a, b, ans = 1e9, w[N], d[N], f[N][N][N][N], g[N][N];
int sq(int x) {return x * x;}
void cmin(int &x, int y) {if(x > y) x = y;}
int main() {
cin >> n >> a >> b, mem(f, 63, N), mem(g, 63, N);
for(int i = 1; i <= n; i++) cin >> w[i], d[i] = w[i];
sort(d + 1, d + n + 1);
for(int i = 1; i <= n; i++) w[i] = lower_bound(d + 1, d + n + 1, w[i]) - d, cerr << w[i] << endl;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= w[i]; j++)
for(int k = w[i]; k <= n; k++)
f[i][i][j][k] = 0, g[i][i] = a;
for(int len = 2; len <= n; len++)
for(int l = 1, r = len; r <= n; l++, r++) {
for(int x = 1; x <= n; x++)
for(int y = x; y <= n; y++) {
for(int k = l; k < r; k++)
cmin(f[l][r][x][y], min(f[l][k][x][y] + f[k + 1][r][x][y],
min(f[l][k][x][y] + g[k + 1][r], g[l][k] + f[k + 1][r][x][y]))),
cmin(g[l][r], f[l][r][x][y] + a + b * sq(d[y] - d[x]));
}
}
cout << g[1][n] << endl;
return 0;
}
*XXVIII. P2339 [USACO04OPEN]Turning in Homework G
神仙区间 DP。考虑到如果从小区间转移到大区间是难以考虑的,于是我们借用 “关路灯” 的 DP 方法:从大区间转移到小区间。
设
时间复杂度
const int N = 1e3 + 5;
int ans = 1e9 + 7, c, h, b, rm, f[N][N][2], p[N];
void cmin(int &x, int y) {if(x > y) x = y;}
void cmax(int &x, int y) {if(x < y) x = y;}
int main() {
cin >> c >> h >> b, h = 0;
for(int i = 1; i <= c; i++) {
int x, t; cin >> x >> t;
p[x] = max(p[x], t), h = max(h, x);
} mem(f, 63, N);
f[0][h][0] = 0, f[0][h][1] = h;
for(int len = h; len; len--)
for(int l = 0, r = len; r <= h; l++, r++) {
cmax(f[l][r][0], p[l]), cmax(f[l][r][1], p[r]);
cmin(f[l + 1][r][0], f[l][r][0] + 1);
cmin(f[l + 1][r][1], f[l][r][0] + r - l);
cmin(f[l][r - 1][1], f[l][r][1] + 1);
cmin(f[l][r - 1][0], f[l][r][1] + r - l);
}
for(int i = 0; i <= h; i++)
ans = min(ans, max(max(f[i][i][0], f[i][i][1]), p[i]) + abs(b - i));
cout << ans << endl;
return 0;
}
*XXIX. BZOJ4350 括号序列再战猪猪侠
仍然是神仙区间 DP。
我们称
设
- 第
个左括号匹配的右括号放在区间 右边。 。前提是 对 没有限制。 - 第
个左括号匹配的右括号放在它右边, 。前提是 对 没有限制。 - 第
个左括号匹配的右括号放在区间 右边, 。前提是 对 没有限制 且 对 没有限制。
时间复杂度
为什么没有想到正解:不知道怎么处理
const int N = 300 + 5;
const int mod = 998244353;
int T, n, m, f[N][N], s[N][N];
void add(int &x, int y) {
x += y; if(x >= mod) x -= mod;
}
bool Inexist(int x1, int x2, int y1, int y2) {
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] == 0;
}
int main() {
cin >> T;
while(T--) {
cin >> n >> m, mem(f, 0, N), mem(s, 0, N);
for(int i = 1, a, b; i <= m; i++)
cin >> a >> b, s[a][b] += 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
bool oops = 0;
for(int i = 1; i <= n; i++) {
f[i][i] = 1;
if(!Inexist(i, i, i, i)) oops = 1;
} if(oops) {puts("0"); continue;}
for(int len = 2; len <= n; len++)
for(int l = 1, r = len; r <= n; l++, r++) {
if(Inexist(l + 1, r, l, l)) add(f[l][r], f[l + 1][r]);
if(Inexist(l, l, l + 1, r)) add(f[l][r], f[l + 1][r]);
for(int k = l + 1; k < r; k++)
if(Inexist(k + 1, r, l, k) && Inexist(l, l, l + 1, k))
add(f[l][r], 1ll * f[l + 1][k] * f[k + 1][r] % mod);
}
cout << f[1][n] << endl;
}
return 0;
}
*XXX. P3607 [USACO17JAN]Subsequence Reversal
神仙区间 DP 题。
一个关键的 observatoin 是翻转一个子序列相当于交换若干对数,而 交换的数的位置之间只有包含关系,不交叉。这就为我们提供了区间 DP 的雏形。
同时注意到不降子序列是与值域有关的,所以值域也应放在 DP 状态中框起来。因此我们设计
。 。 。这意味这交换两个 。 。
注意 DP 顺序。时间复杂度
为什么没有想到正解:没有注意到关键 observation 并想复杂了。其实很多区间 DP 比想象中简单,只是状态设计较为困难。
const int N = 50 + 5;
int n, a[N], f[N][N][N][N];
void cmax(int &x, int y) {if(y > x) x = y;}
int main() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= a[i]; j++)
for(int k = a[i]; k <= n; k++)
f[i][i][j][k] = 1;
for(int len = 2; len <= n; len++)
for(int l = 1, r = len; r <= n; l++, r++)
for(int y = 1; y < N; y++)
for(int x = y; x; x--) {
f[l][r][x][y] = max(f[l][r][x + 1][y], f[l][r][x][y - 1]);
cmax(f[l][r][x][y], f[l + 1][r][x][y] + (a[l] == x));
cmax(f[l][r][x][y], f[l][r - 1][x][y] + (a[r] == y));
cmax(f[l][r][x][y], f[l + 1][r - 1][x][y] + (a[l] == y) + (a[r] == x));
}
cout << f[1][n][1][N - 1] << endl;
return 0;
}
*XXXI. P4766 [CERC2014]Outer space invaders
神仙区间 DP 题。
一个显然的 observation 是将时刻离散化,因为我们只关心外星人出现与发动攻击的相对顺序。
题解区 Cry_For_theMoon 的 题解 总结的很棒:
区间 DP 的另一大套路就是:当我不好 "拆分-合并" 的时候,先考虑最后执行的操作,再用这个最后执行的操作把区间分成两部分,可以视作 "合并-拆分"。
因此,求
时间复杂度
为什么没有想到正解:没有接触过枚举最后一次操作后分裂区间的区间 DP。
const int N = 600 + 5;
int T, n, a[N], b[N], d[N], f[N][N];
int cnt, c[N];
int main() {
cin >> T;
while(T--) {
cin >> n, cnt = 0, mem(f, 63, N);
for(int i = 1; i <= n; i++)
for(int j = 1; j < i; j++) f[i][j] = 0;
for(int i = 1; i <= n; i++)
cin >> a[i] >> b[i] >> d[i], c[++cnt] = a[i], c[++cnt] = b[i];
sort(c + 1, c + cnt + 1), cnt = unique(c + 1, c + cnt + 1) - c - 1;
for(int i = 1; i <= n; i++)
a[i] = lower_bound(c + 1, c + cnt + 1, a[i]) - c,
b[i] = lower_bound(c + 1, c + cnt + 1, b[i]) - c;
for(int len = 1; len <= cnt; len++)
for(int l = 1, r = len; r <= cnt; l++, r++) {
int mxid = -1;
for(int i = 1; i <= n; i++)
if(l <= a[i] && b[i] <= r && (mxid == -1 || d[i] > d[mxid])) mxid = i;
if(mxid == -1) {f[l][r] = 0; continue;}
for(int i = a[mxid]; i <= b[mxid]; i++)
f[l][r] = min(f[l][r], f[l][i - 1] + f[i + 1][r] + d[mxid]);
}
cout << f[1][cnt] << endl;
}
return 0;
}
*XXXII. P3592 [POI2015]MYJ
区间 DP 好题。和上面一题差不多的套路。
首先离散化
那么转移就挺 trivial 的:枚举断点
时间复杂度
const int N = 50 + 5;
const int M = 4000 + 5;
int n, m, cnt, a[M], b[M], c[M], d[M], res[N];
int f[N][N][M], mx[N][N][M], de[N][N][M];
void solve(int l, int r, int k) {
if(l > r) return;
if(l == r) return res[l] = d[k], void();
int p = de[l][r][k]; res[p] = d[k];
assert(p > 0);
solve(l, p - 1, mx[l][p - 1][k]);
solve(p + 1, r, mx[p + 1][r][k]);
}
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++)
cin >> a[i] >> b[i] >> c[i], d[i] = c[i];
sort(d + 1, d + m + 1), cnt = unique(d + 1, d + m + 1) - d - 1;
for(int i = 1; i <= m; i++) c[i] = lower_bound(d + 1, d + cnt + 1, c[i]) - d;
for(int len = 1; len <= n; len++)
for(int l = 1, r = len; r <= n; l++, r++) {
for(int k = l; k <= r; k++) {
static int num[M]; mem(num, 0, cnt + 2);
for(int p = 1; p <= m; p++)
num[c[p]] += l <= a[p] && a[p] <= k && k <= b[p] && b[p] <= r;
for(int p = cnt; p; p--) {
int res = (num[p] += num[p + 1]) * d[p] +
f[l][k - 1][p] + f[k + 1][r][p];
if(res >= f[l][r][p]) f[l][r][p] = res, de[l][r][p] = k;
}
}
for(int i = cnt; i; i--) {
if(f[l][r][i + 1] > f[l][r][i])
f[l][r][i] = f[l][r][i + 1], mx[l][r][i] = mx[l][r][i + 1];
else mx[l][r][i] = i;
}
}
cout << f[1][n][1] << endl;
solve(1, n, mx[1][n][1]);
for(int i = 1; i <= n; i++) cout << res[i] << " ";
cout << endl;
return 0;
}
XXXIII. P5774 [JSOI2016]病毒感染
题解。
*XXXIV. BZOJ3971 [WF2013]Матрёшка
题解。
*XXXV. P7163 [COCI2020-2021#2] Svjetlo
题解。
*XXXVI. P6116 [JOI 2019 Final]家庭菜園
一道比较简单但细节的 DP。
实际上我们只关心当前位置摆放的花盆颜色,以及在此之前三种颜色的花盆分别有多少个。并且同种颜色花盆的相对位置不会改变。因此设
转移直接枚举下一个位置的花盆颜色
时空间复杂度
const int N = 400 + 5;
const int inf = 1e9;
int n, a, b, c, pos[3][N], f[3][135][205][N];
vint buc[3];
char s[N];
int main() {
cin >> n >> s + 1;
for(int i = 1; i <= n; i++)
s[i] == 'R' ? buc[0].pb(i) : s[i] == 'G' ? buc[1].pb(i) : buc[2].pb(i);
sort(buc, buc + 3, [](vint a, vint b) {return a.size() < b.size();});
for(int i = 0; i < 3; i++) for(int it : buc[i]) pos[i][it]++;
for(int i = 1; i <= n; i++) for(int j = 0; j < 3; j++) pos[j][i] += pos[j][i - 1];
mem(f, 0x3f, 3), f[0][0][0][0] = 0;
a = buc[0].size(), b = buc[1].size(), c = buc[2].size();
for(int i = 0; i <= a; i++) for(int j = 0; j <= b; j++)
for(int k = 0, np = i + j + 1; k <= c; k++, np++) for(int p = 0; p < 3; p++) {
if(f[p][i][j][k] < inf) for(int q = 0; q < 3; q++)
if(p != q || (!i && !j && !k)) {
int val = f[p][i][j][k];
if(q == 0 && i < a) {
int Pos = buc[q][i];
int Swp = max(0, j - pos[1][Pos]) + max(0, k - pos[2][Pos]);
cmin(f[q][i + 1][j][k], val + max(0, Pos + Swp - np));
} if(q == 1 && j < b) {
int Pos = buc[q][j];
int Swp = max(0, i - pos[0][Pos]) + max(0, k - pos[2][Pos]);
cmin(f[q][i][j + 1][k], val + max(0, Pos + Swp - np));
} if(q == 2 && k < c) {
int Pos = buc[q][k];
int Swp = max(0, i - pos[0][Pos]) + max(0, j - pos[1][Pos]);
cmin(f[q][i][j][k + 1], val + max(0, Pos + Swp - np));
}
}
}
int ans = min(min(f[0][a][b][c], f[1][a][b][c]), f[2][a][b][c]);
cout << (ans >= inf ? -1 : ans) << endl;
return 0;
}
XXXVII. P7914 [CSP-S 2021] 括号序列
设 ()()()
这种情况会算重,那么设
const int N = 500 + 5;
const int mod = 1e9 + 7;
void add(int &x, int y) {x += y; if(x >= mod) x -= mod;}
int n, K, f[N][N], g[N][N], br[N][N], h[N][N]; // g : AS
char s[N];
bool mt(int p, char t) {return s[p] == '?' || s[p] == t;}
bool Med;
int main() {
cin >> n >> K >> s + 1;
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j++) {
if(j - i + 1 > K) continue;
h[i][j] = 1;
for(int k = i; k <= j; k++) h[i][j] &= mt(k, '*');
}
for(int i = 1; i <= n + 1; i++) f[i][i - 1] = br[i][i - 1] = 1;
for(int len = 2; len <= n; len++)
for(int l = 1, r = len; r <= n; l++, r++) {
int tmpf = 0, tmpbr = 0;
for(int d = l; d < r; d++) {
add(tmpf, 1ll * f[l][d] * br[d + 1][r] % mod); // case 2.1
add(tmpf, 1ll * g[l][d] * br[d + 1][r] % mod); // case 2.2
if(h[d + 1][r]) add(g[l][r], f[l][d]);
}
if(mt(l, '(') && mt(r, ')')) {
add(tmpbr, h[l + 1][r - 1]); // case 1
add(tmpbr, f[l + 1][r - 1]); // case 3.1
for(int d = l + 1; d + 1 < r; d++) {
if(h[l + 1][d]) add(tmpbr, f[d + 1][r - 1]); // case 3.2
if(h[d + 1][r - 1]) add(tmpbr, f[l + 1][d]); // case 3.3
}
}
f[l][r] = tmpf, br[l][r] = tmpbr;
add(f[l][r], br[l][r]);
}
cout << f[1][n] << endl;
return flush(), 0;
}
*XXXVIII. 2021 联考模拟北大附 图的直径
题意简述:两条
个点的链,点 与 之间的边权分别为 。求添加不超过 条连接编号相同的点的边后图的直径最小值。 , 。
形象地想一下,若
此时就需要挖掘一些性质了:首先,注意到转移是一个对决策取
然后是一步非常关键的操作:根据单调性将值域与定义域对换。这里的单调性指的是若
此外还需预处理一段区间
Bonus:是否可以通过决策单调性优化做到
启发:遇到与最值有关的最优性 DP 时可以尝试先二分答案转化为判定性问题,然后一定单调性将定义域与值域互换从而优化复杂度。思路:设计朴素 DP(赛时这一步没做到)
#include <bits/stdc++.h>
using namespace std;
template <class T1, class T2> void cmin(T1 &a, T2 b){a = a < b ? a : b;}
template <class T1, class T2> void cmax(T1 &a, T2 b){a = a > b ? a : b;}
int n, m, k, a[233], b[233], da[233][233], db[233][233], dia[233][233], frl[233][233], frr[233][233];
bool check(int x) {
static int f[233][233]; memset(f, 0x3f, sizeof(f)); for(int i = 0; i <= n; i++) f[i][1] = da[0][i] + db[0][i] <= x ? max(da[0][i], db[0][i]) : 1e9;
for(int i = 1; i <= n; i++) for(int j = 2; j <= i + 1; j++) for(int k = 0; k < i; k++) if(dia[k][i] <= x && f[k][j - 1] + frl[k][i] <= x) cmin(f[i][j], max(frr[k][i], f[k][j - 1] + min(da[k][i], db[k][i])));
for(int i = 0; i <= n; i++) if(f[i][k] + max(da[i][n], db[i][n]) <= x && da[i][n] + db[i][n] <= x) return 1; return 0;
}
int main(){
cin >> n >> k; for(int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1]; for(int i = 1; i <= n; i++) cin >> b[i], b[i] += b[i - 1];
for(int i = 0; i < n; i++) for(int j = i + 1; j <= n; j++) da[i][j] = a[j] - a[i]; for(int i = 0; i < n; i++) for(int j = i + 1; j <= n; j++) db[i][j] = b[j] - b[i];
for(int i = 0; i < n; i++) for(int j = i + 1; j <= n; j++) for(int k = i; k <= j; k++) cmax(frl[i][j], max(min(da[i][k], da[k][j] + db[i][j]), min(db[i][k], db[k][j] + da[i][j])));
for(int i = 0; i < n; i++) for(int j = i + 1; j <= n; j++) for(int k = i; k <= j; k++) cmax(frr[i][j], max(min(da[k][j], da[i][k] + db[i][j]), min(db[k][j], db[i][k] + da[i][j])));
for(int i = 0; i < n; i++) for(int j = i + 1; j <= n; j++) {
for(int k = i, p = i; k <= j; k++) {while(p < j && min(da[k][p], da[i][k] + da[p][j] + db[i][j]) < min(da[k][p + 1], da[i][k] + da[p + 1][j] + db[i][j])) p++; cmax(dia[i][j], min(da[k][p], da[i][k] + da[p][j] + db[i][j]));}
for(int k = i, p = i; k <= j; k++) {while(p < j && min(db[k][p], db[i][k] + db[p][j] + da[i][j]) < min(db[k][p + 1], db[i][k] + db[p + 1][j] + da[i][j])) p++; cmax(dia[i][j], min(db[k][p], db[i][k] + db[p][j] + da[i][j]));}
for(int k = i, p = j; k <= j; k++) {while(p > i && min(da[k][j] + db[p][j], da[i][k] + db[i][p]) < min(da[k][j] + db[p - 1][j], da[i][k] + db[i][p - 1])) p--; cmax(dia[i][j], min(da[k][j] + db[p][j], da[i][k] + db[i][p]));}
for(int k = i, p = j; k <= j; k++) {while(p > i && min(db[k][j] + da[p][j], db[i][k] + da[i][p]) < min(db[k][j] + da[p - 1][j], db[i][k] + da[i][p - 1])) p--; cmax(dia[i][j], min(db[k][j] + da[p][j], db[i][k] + da[i][p]));}
} int l = 0, r = 100 * n; while(l < r) {int m = l + r >> 1; check(m) ? r = m : l = m + 1;}
return cout << l << endl, 0;
}
XXXIX. 2021 联考模拟巴蜀中学 叁仟柒佰万
题意简述:求分割一个序列使得每段
值相等的方案数对 取模。
。
看到题目,一个首先的想法是考察哪些值可能成为最终的
不妨设全局
启示:从题目给出的限制反推出性质是很有用的方法。
const int N = 4e7 + 5;
const int mod = 1e9 + 7;
int a[N], buc[N], f[N];
void solve() {
int n = read(), mex = 0, cur = 0;
if(n == 37000000) {
int x = read(), y = read(); a[1] = 0;
for(int i = 2; i <= n; i++) a[i] = (1ll * a[i - 1] * x + y + i) & 262143;
} else for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 0; i <= n + 4; i++) buc[i] = 0;
for(int i = 1; i <= n; i++) buc[a[i]] = 1;
while(buc[mex]) mex += 1;
if(!mex) {
int ans = 1, b = 2; n--;
while(n) {
if(n & 1) ans = 1ll * ans * b % mod;
b = 1ll * b * b % mod, n >>= 1;
} return cout << ans << endl, void();
}
for(int i = 0; i <= n + 4; i++) buc[i] = 0; f[0] = 1;
for(int i = 1, pre = 1; i <= n; i++) {
buc[a[i]]++;
while(buc[cur]) cur++;
if(cur == mex) {
while(1) {
if(a[pre] < mex && buc[a[pre]] == 1) break;
buc[a[pre]]--, pre++;
} f[i] = (f[pre - 1] + f[i - 1]) % mod;
} else f[i] = f[i - 1];
}
cout << (f[n] - f[n - 1] + mod) % mod << endl;
}
*XL. CF1584F Strange LCS *2600
仍然是从简化版入手。考虑弱化版 “每个字符只出现一次” 怎么做:对于两个字符
不难将其扩展到本题的做法:只需要再记录一个 mask
mask
(这里用了贪心的思想,能靠前尽量靠前显然更优)即可。时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define pii pair <int, int>
#define mem(x, v, s) memset(x, v, sizeof(x[0]) * (s))
const int N = 10;
const int S = 110;
int T, n, ap, aS, len[N], mp[1 << N];
int f[S][1 << N], buc[N][S], p[N][S][2];
pii tr[S][1 << N];
char s[N][S];
void print(int i, int S) {
if(!i) return;
print(tr[i][S].first, tr[i][S].second), putchar(s[0][i - 1]);
}
int main(){
for(int i = 0; i < 26; i++) mp['a' + i] = i;
for(int i = 0; i < 26; i++) mp['A' + i] = i + 26;
cin >> T;
while(T--) {
cin >> n, mem(f, 0, S), mem(buc, 0, N), ap = aS = 0;
for(int i = 0; i < n; i++) {
cin >> s[i], len[i] = strlen(s[i]);
for(int j = 0; j < len[i]; j++) {
int id = mp[s[i][j]];
p[i][id][buc[i][id]++] = j;
}
} for(int i = 0; i < len[0]; i++)
for(int S = 0; S < 1 << n; S++) if(!i || f[i][S])
for(int j = i + 1; j <= len[0]; j++) {
int ok = 1, msk = 0, pr = i ? mp[s[0][i - 1]] : 0, su = mp[s[0][j - 1]];
for(int q = 0; q < n && ok; q++) {
int b = S >> q & 1;
if(!buc[q][su] || i && buc[q][pr] <= b) {ok = 0; break;}
if(!i) continue;
int cp = p[q][pr][b], fd = -1;
for(int k = 0; k < buc[q][su] && fd == -1; k++)
if(p[q][su][k] > cp) fd = k;
fd == -1 ? ok = 0 : msk |= fd << q;
} int v = !i ? 1 : f[i][S] + 1;
if(ok && f[j][msk] < v) {
f[j][msk] = v, tr[j][msk] = {i, S};
if(v > f[ap][aS]) ap = j, aS = msk;
}
} cout << f[ap][aS] << "\n", print(ap, aS), puts("");
}
return 0;
}
*XLI. AT3878 [ARC089D] ColoringBalls *3782
神题啊。第一步要想到将颜色相同的连续段缩成一个点,变成形如
不妨设分配完之后被分配的第
确定
对
启示:对于操作序列相关计数题,考虑哪些局面可以得到,并抛弃所有不必要的信息。如果一些不能确定的变量阻碍了思路,让问题看起来不可做,那就尝试枚举它。
const int N = 70 + 5;
const int mod = 1e9 + 7;
void add(int &x, int y) {x += y, x >= mod && (x -= mod);}
void add(int &x, int y, int z) {x = y + z, x >= mod && (x -= mod);}
int n, k, ans, vis[N], sum[N];
int f[N][N][N], C[N << 1][N << 1];
char s[N];
bool init(int x, int y) {
int cnt1 = 0, cnt2 = 0;
for(int i = 0; i < k; i++) {
if(s[i] == 'r') {
if(cnt1 < x + y) cnt1++, vis[i] = 0;
else vis[i] = 1;
} else {
if(cnt2 < x && cnt2 < cnt1) cnt2++, vis[i] = 0;
else vis[i] = 1;
}
} if(cnt1 < x + y || cnt2 < x) return 0;
for(int i = k - 1, p = x, num = 0; ~i; i--)
if(vis[i]) num++;
else if(s[i] == 'b') sum[p--] = num;
return 1;
}
int main(){
cin >> n >> k >> s, C[0][0] = 1;
for(int i = 1; i < N << 1; i++)
for(int j = 0; j <= i; j++)
add(C[i][j], C[i - 1][j], j ? C[i - 1][j - 1] : 0);
for(int x = 0; x <= k; x++)
for(int y = 0; y <= k; y++)
if(init(x, y)) {
int lim = sum[1]; mem(f, 0, N);
for(int k = 0; k <= lim; k++) f[x + 1][0][k] = 1;
for(int i = x; i; i--) {
for(int k = 0; k <= lim; k++) f[i][0][k] = 1;
for(int j = 1; j <= sum[i] && (j + x + y << 1) - 1 <= n; j++)
for(int k = 1; k <= lim; k++) {
for(int l = 1; i + l <= x + 1 && k * l <= j && sum[i + l] >= j - k * l; l++)
add(f[i][j][k], 1ll * f[i + l][j - k * l][k - 1] * C[x - i + 1][l] % mod);
add(f[i][j][k], f[i][j][k - 1]);
}
} int res = 0;
for(int j = 0; j <= lim; j++)
add(res, 1ll * f[1][j][lim] * C[n + (x << 1) + 1][(x << 2) + (y + j << 1)] % mod);
add(ans, 1ll * res * C[x + y][y] % mod);
}
return 0;
}
*XLII. CF1613D MEX Sequences
好题。乍一看没啥思路,考虑挖掘性质:
-
observation 1. 若出现
,则之后不可能出现 。不妨设
,显然 等于 或 。无论哪种情况在添加 后 都一定 ,不符题意。 -
observation 2. 若
,那么 只能为 或 。因为
不能为 ,而 又没有出现过所以接下来的 值只能为 。得证。 -
observation 3. 用
表示连续若干个 ,符合题意的序列一定满足形如 或者 ,其中括号是不需要出现的部分。-
不可能出现。 -
根据 observation 1 不可能出现
。 -
若
,此时 值一定为 。那么前面必然是从 连续递增到 的一段,因为若前面这一段有下降的部分,那么只能是下降 ,但根据 observation 2 接下来 不可能上升,不符合题意。 -
或 是平凡的。 -
只能出现在 observation 2 所述的情况中,不会使 增加。 -
不可能出现。
的情况平凡。证毕。 -
考虑设
接下来只需要考虑对于每个位置
统计答案就比较简单了,首先正序处理出
XLIII. [BZOJ1402] Ticket to Ride
我们设
这不就是最小斯坦纳树么。后者按
const int N = 30 + 5;
int n, m, ans = 1e9, f[N][1 << 8], vis[N];
vpii e[N];
string s, t;
gp_hash_table <string, int> mp;
void Dijkstra(int S) {
priority_queue <pii, vector <pii>, greater <pii>> q;
for(int i = 1; i <= n; i++) if(f[i][S] < 1e9) q.push({f[i][S], i});
mem(vis, 0, N);
while(!q.empty()) {
pii t = q.top(); q.pop();
int id = t.se, ds = t.fi;
if(vis[id]) continue;
vis[id] = 1;
for(pii it : e[id]) {
int to = it.fi, d = ds + it.se;
if(f[to][S] > d) q.push({f[to][S] = d, to});
}
}
}
void dfs(int tot, int S) {
if(S == 15) return cmin(ans, tot), void();
for(int T = (15 - S), msk = 0; T; T = (T - 1) & (15 - S), msk = 0) {
for(int i = 0; i < 4; i++)
if(T >> i & 1) msk |= (1 << i * 2) | (1 << i * 2 + 1);
for(int i = 1; i <= n; i++) dfs(tot + f[i][msk], S | T);
}
}
int main() {
cin >> n >> m, mem(f, 0x3f, N);
for(int i = 1; i <= n; i++) cin >> s, mp[s] = i;
for(int i = 1, w; i <= m; i++)
cin >> s >> t >> w, e[mp[s]].pb(mp[t], w), e[mp[t]].pb(mp[s], w);
for(int i = 0; i < 4; i++)
cin >> s >> t, f[mp[s]][1 << i * 2] = f[mp[t]][1 << i * 2 + 1] = 0;
for(int S = 1; S < 1 << 8; S++) {
for(int p = 1; p <= n; p++)
for(int T = S; T; T = (T - 1) & S)
cmin(f[p][S], f[p][T] + f[p][S - T]);
Dijkstra(S);
} dfs(0, 0), cout << ans << endl;
return flush(), 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现