2026.2.16 - 2026.2.22 日做题题解
P3647 [APIO2014] 连珠线 / 换根 DP
容易发现蓝线的长度均为 \(3\),而对于一个定根,蓝线分为两种:连接一个点和其两个儿子,连接一个点的父亲和一个儿子。
而我们发现,如果存在第一种节点,我们可能无法通过题目中的操作完成,但是我们发现如果只用第二种操作然后对于不同的根求解答案一定能覆盖所有的情况。
假设以 \(1\) 为根。
设 \(f_{u,0/1}\) 表示以 \(u\) 为根的子树,且父亲是否为蓝线的中心。
然后换根使容易的。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int N=2e5+10;
#define pii pair<int,int>
vector<pii>g[N];
int n,f[N],ans;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
inline int max(int a,int b,int c){return max(max(a,b),c);}
multiset<int,greater<int> >s[N];
inline void tfer(int u,int v,int w,int op){
if (op) f[u]+=max(f[v],f[v]+*s[v].begin()+w),s[u].insert(f[v]+w-max(f[v],f[v]+*s[v].begin()+w));
else f[u]-=max(f[v],f[v]+*s[v].begin()+w),s[u].erase(s[u].find(f[v]+w-max(f[v],f[v]+*s[v].begin()+w)));
}
void dfs1(int u,int fa){
s[u].insert(-1e18);
for (auto i:g[u]) if (i.first^fa) dfs1(i.first,u),tfer(u,i.first,i.second,1);
}
void dfs2(int u,int fa){
ans=max(ans,f[u]);
for (auto i:g[u]) if (i.first^fa){
int v=i.first,w=i.second;
tfer(u,v,w,0),tfer(v,u,w,1);
dfs2(v,u);
tfer(v,u,w,0),tfer(u,v,w,1);
}
}
main(){
n=read();
for (int i=1,u,v,w;i<n;i++) u=read(),v=read(),w=read(),g[u].push_back({v,w}),g[v].push_back({u,w});
dfs1(1,0),dfs2(1,0);
cout <<ans<<endl;
return 0;
}
P10962 Computer / 换根 dp
考虑换根,假设现在以 \(1\) 为根。
设 \(f_u\) 表示已 \(u\) 为根的子树到其子树的点的最远距离。
直接利用 multiset 换根即可,注意本题多测。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1e4+10;
#define pii pair<int,int>
multiset<int,greater<int>>s[N];vector<pii>g[N];
int n;
inline void tfer(int u,int v,int w,int op){
if (op==1) s[u].insert(*s[v].begin()+w);
else s[u].erase(s[u].find(*s[v].begin()+w));
}
void dfs1(int u,int fa){
s[u].insert(0);
for (auto [v,w]:g[u]) if (v^fa) dfs1(v,u),tfer(u,v,w,1);
}
int anss[N];
void dfs2(int u,int fa){
anss[u]=*s[u].begin();
for (auto [v,w]:g[u]) if (v^fa){
tfer(u,v,w,0),tfer(v,u,w,1);
dfs2(v,u);
tfer(v,u,w,0),tfer(u,v,w,1);
}
}
main(){
while(cin>>n){
for (int i=1;i<=n;i++) s[i].clear(),g[i].clear();
for (int i=2,v,w;i<=n;i++) v=read(),w=read(),g[i].push_back({v,w}),g[v].push_back({i,w});
dfs1(1,0),dfs2(1,0);
for (int i=1;i<=n;i++) printf("%lld\n",anss[i]);
}
return 0;
}
P3047 [USACO12FEB] Nearby Cows G / 换根 dp
考虑换根。
假设以 \(1\) 为根,设 \(f_{u,i}\) 表示以 \(u\) 为根的子树中距离 \(u\) 为 \(i\) 的权值和。
然后换根一下即可。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1e5+10,M=22;
int f[N][M],n,k,w[N];
vector<int>g[N];
int anss[N];
inline void tfer(int u,int v,int op){for (int i=1;i<=k;i++) f[u][i]+=op*f[v][i-1];}
void dfs1(int u,int fa){
f[u][0]=w[u];
for (auto v:g[u]) if (v^fa) dfs1(v,u),tfer(u,v,1);
}
void dfs2(int u,int fa){
for (int i=0;i<=k;i++) anss[u]+=f[u][i];
for (auto v:g[u]) if (v^fa){
tfer(u,v,-1),tfer(v,u,1);
dfs2(v,u);
tfer(v,u,-1),tfer(u,v,1);
}
}
main(){
n=read(),k=read();
for (int i=1,u,v;i<n;i++) u=read(),v=read(),g[u].push_back(v),g[v].push_back(u);
for (int i=1;i<=n;i++) w[i]=read();
dfs1(1,0),dfs2(1,0);
for (int i=1;i<=n;i++) printf("%lld\n",anss[i]);
return 0;
}
P4127 [AHOI2009] 同类分布 & P10959 月之谜 / 数位 dp
首先显然考虑数位 dp,容易发现一个数的数位之和不会太大,最多就 \(9len\),那么考虑枚举数位大小之和,然后跑 dp。
设 \(f_{i,j,k,0/1}\) 表示前 \(i\) 个数位,总和对当前枚举的大小取模的值为 \(j\),目前计算的数位的总和为 \(j\),且当前是否紧贴着高位。
直接枚举然后清空 \(f\),直接记搜就可以过 P4127。
由于每次我们需要便利整个 \(f\) 而多测其实很多值是可以利用的,我们可以将枚举的数位也加入状态,然后记搜即可。
注意我们只有没有紧贴高位才可以查表中的答案,因为如果紧贴高位而贴的高位可能不一样而导致错误。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
int f[20][83][83][83][2],a[20],n;
int dfs(int i,int sum1,int sum2,int p,bool flag){
if (i==0) return (sum1==0&&sum2==p);
if (flag&&~f[i][sum1][sum2][p][flag]) return f[i][sum1][sum2][p][flag];
int ans=0,now=(!flag?a[i]:9);
for (int j=0;j<=now;j++) ans+=dfs(i-1,(sum1*10+j)%p,sum2+j,p,flag||(j<now));
return f[i][sum1][sum2][p][flag]=ans;
}
inline int solve(int x){
n=0;
while(x) a[++n]=x%10,x/=10;
int ans=0;
for (int p=81;p>0;p--) ans+=dfs(n,0,0,p,0);
return ans;
}
main(){
memset(f,-1,sizeof(f));
int x,y;
while(cin>>x>>y) printf("%lld\n",solve(y)-solve(x-1));
return 0;
}
P13085 [SCOI2009] windy 数(加强版)
直接数位 dp 即可。
设 \(f_{i,last,0/1,0/1}\) 表示考虑了前 \(i\) 个高位,前一个值为 \(last\),且目前是否是前导 \(0\) 部分以及是否紧贴高位,然后随便转移一下即可。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
int f[20][20][2][2],a[20],n;
int dfs(int i,int last,bool qd,bool flag){
if (i==0) return 1;
if (~f[i][last][qd][flag]) return f[i][last][qd][flag];
int now=(!flag?a[i]:9),ans=0;
for (int j=0;j<=now;j++){
if (qd) ans+=dfs(i-1,j,(j==0),flag||(j<now));
else if (abs(last-j)>=2) ans+=dfs(i-1,j,qd,flag||(j<now));
}
return f[i][last][qd][flag]=ans;
}
inline int solve(int x){
memset(f,-1,sizeof(f));
n=0;while(x) a[++n]=x%10,x/=10;
return dfs(n,0,1,0);
}
main(){
int x=read(),y=read();
printf("%lld",solve(y)-solve(x-1));
return 0;
}
CF16E Fish / 状压 dp & 概率 dp
首先 \(n\) 很小,考虑状压。
设 \(f_S\) 表示目前活着的鱼的状态为 \(S\) 的概率。
转移枚举两个鱼 \(i,j\),假设 \(i\) 将 \(j\) 吃掉。
复杂度 \(O(2^n)\)。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1<<18;
double a[20][20],f[N];
main(){
int n=read();
for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%Lf",&a[i][j]);
f[(1<<n)-1]=1;
for (int s=(1<<n)-1;s>0;s--){
int cnt=__builtin_popcountll(s);
for (int i=1;i<=n;i++) if ((s>>i-1)&1) for (int j=1;j<=n;j++) if ((s>>j-1)&1) if (i^j) f[s^(1<<j-1)]+=f[s]*2.0*a[i][j]/(cnt*(cnt-1));
}
for (int i=1;i<=n;i++) printf("%.10Lf ",f[1<<i-1]);
return 0;
}
P4091 [HEOI2016/TJOI2016] 求和 / NTT
首先你要知道。
然后就可以开始推式子。
然后后面就是一个等比数列求和,一个 ntt 即可,注意特判一下 \(k=1\) 的情况。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=3e5+10,mod=998244353,G=3,invG=(mod+1)/3;
int r[N],n,a[N],b[N],pw[N],fac[N],invfac[N];
inline int qpow(int a,int b){int s=1;while(b) (b&1)?s=s*a%mod:1,a=a*a%mod,b>>=1;return s;}
void exgcd(int a,int b,int &x,int &y){if (b==0) x=1,y=0;else exgcd(b,a%b,y,x),y-=a/b*x;}
inline int inv(int a){int x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod;}
inline void ntt(int *a,int n,int op){
for (int i=0;i<n;i++) if (i<r[i]) swap(a[i],a[r[i]]);
for (int i=2;i<=n;i<<=1){
int g1=qpow(op==1?G:invG,(mod-1)/i);
for (int j=0;j<n;j+=i){
int gk=1;
for (int k=0,x,y;k<(i>>1);k++,gk=gk*g1%mod) x=a[j+k],y=a[j+k+i/2]*gk%mod,a[j+k]=(x+y)%mod,a[j+k+i/2]=(x-y+mod)%mod;
}
}
}
inline void mul(int *a,int *b,int n,int m){
int len=1,lg=0;
while(len<=n+m) len<<=1,lg++;
for (int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<lg-1);
ntt(a,len,1),ntt(b,len,1);
for (int i=0;i<len;i++) a[i]=a[i]*b[i]%mod;
ntt(a,len,-1);
int tmp=inv(len);for (int i=0;i<len;i++) a[i]=a[i]*tmp%mod;
}
main(){
n=read();
fac[0]=invfac[0]=pw[0]=1;
for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod,invfac[i]=invfac[i-1]*inv(i)%mod,pw[i]=pw[i-1]*(mod-1)%mod;
for (int i=0;i<=n;i++) a[i]=invfac[i]*(qpow(i,n+1)+mod-1)%mod*inv(i-1+mod)%mod,b[i]=pw[i]*invfac[i]%mod;
a[1]=n+1;
mul(a,b,n,n);
int ans=0;
for (int i=0;i<=n;i++) ans=(ans+qpow(2,i)*fac[i]%mod*a[i]%mod)%mod;
cout <<ans;
return 0;
}

浙公网安备 33010602011771号