CF512D Fox And Travelling
題目描述
給出一張無向圖,每次你可以選擇一個度數 \(\leq 1\) 的點並將其刪除。
問對於 \(k=0,1,2...n\) 有多少個刪除 \(k\) 個點的序列,答案模 \(10^9+9\)
\(n\leq 100,m\leq \frac{n(n-1)}{2}\)
解法
因為只能刪除度數 \(\leq 1\) 的點,所以環是和刪除序列沒關系的,那么我們把環去掉之后就得到了一個森林,這個過程可以用拓撲排序實現,沒有拓撲掉的點就不需要考慮了。
但是這個森林的樹是不相同的,因為如果有一棵樹連接着環,那么必須要把鏈接的點當成根,最后選取它。但是如果樹沒有連接着環,就沒有根這一說。所以這個森林混合了有根樹和無根樹。
首先考慮有根樹怎么做,樹形 \(dp\) 是顯然的,\(dp[u][i]\) 表示點 \(u\) 內選取了 \(i\) 個點,然后樹背包板。
無根樹怎么做才是重點,根據我以前總結的套路我們可以枚舉根,按照有根樹的方法做樹形 \(dp\),再把得到的結果求和。這樣做顯然會算重,本題的點睛之筆就是考慮一種方案被算重了多少次(直接但實用方法),對於選取了 \(i\) 個點的方案,由於這 \(i\) 個點一定處在樹的邊緣,所以選取其他的 \(size-i\) 個點為根都會算到這個方案,那么我們除以 \(size-i\) 即可。
時間復雜度 \(O(n^3)\),由於我不學多項式所以更優的做法將不再講解。
細節:無向圖的拓撲排序還是增設一個訪問標記吧,然后在 \(deg\leq 1\) 的時候塞到隊列里面。
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 105;
const int MOD = 1e9+9;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,tot,f[M],d[M],b[M],inv[M],fac[M],vis[M];
int dp[M][M],g[M],h[M],s[M],zinv[M],siz[M];
struct edge {int v,next;}e[M*M];
void add(int &x,int y) {x=(x+y)%MOD;}
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=0;i<=n;i++) zinv[i]=inv[i];
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
}
int C(int n,int m)
{
if(n<m || m<0) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
void tpsort()
{
queue<int> q;
for(int i=1;i<=n;i++)
if(d[i]<=1) vis[i]=1,q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;d[v]--;
if(d[v]<=1 && !vis[v]) vis[v]=1,q.push(v);
}
}
}
void dfs(int u,int rt)
{
b[u]=rt;s[rt]++;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!b[v] && !d[v]) dfs(v,rt);
}
}
void get(int u,int fa)
{
for(int i=0;i<=n;i++) dp[u][i]=0;
dp[u][0]=1;siz[u]=1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa || b[u]!=b[v]) continue;
get(v,u);
for(int j=siz[u];j>=0;j--)
for(int k=1;k<=siz[v];k++)
add(dp[u][j+k],dp[u][j]*dp[v][k]%MOD*C(j+k,j));
siz[u]+=siz[v];
}
dp[u][siz[u]]=dp[u][siz[u]-1];
}
signed main()
{
n=read();m=read();init(n);
for(int i=1;i<=m;i++)
{
int u=read(),v=read();d[u]++;d[v]++;
e[++tot]=edge{u,f[v]},f[v]=tot;
e[++tot]=edge{v,f[u]},f[u]=tot;
}
tpsort();
for(int i=1;i<=n;i++)
if(d[i]==1) dfs(i,i);
for(int i=1;i<=n;i++)
if(!d[i] && !b[i]) dfs(i,i);
g[0]=1;
for(int i=1;i<=n;i++) if(i==b[i])
{
for(int j=0;j<=n;j++) h[j]=0;
if(d[i]==1)
{
get(i,0);
for(int j=0;j<=s[i];j++) h[j]=dp[i][j];
}
else
{
for(int o=1;o<=n;o++) if(b[o]==i)
{
get(o,0);
for(int j=0;j<=s[i];j++)
add(h[j],dp[o][j]);
}
for(int j=0;j<=s[i];j++)
h[j]=h[j]*zinv[s[i]-j]%MOD;
}
for(int j=n;j>=0;j--)
for(int k=1;k<=s[i] && j+k<=n;k++)
add(g[j+k],g[j]*h[k]%MOD*C(j+k,j));
}
for(int i=0;i<=n;i++)
printf("%lld\n",g[i]);
}
CF516D Drazil and Morning Exercise
題目描述
解法
首先考慮本題最基本的量 \(f(x)\) 怎么計算,連我都知道的套路是轉化成樹的直徑問題,可以用反證法說明 \(f(x)=dis(x,y)\) 的 \(y\) 一定是直徑的某一個端點。
然后好像沒有什么好的思路,但是考慮如果不要求 \(S\) 的點聯通,可以直接求出 \(f(x)\) 之后排序,然后枚舉最小值,貼着限制能選就選即可。
現在要求 \(S\) 聯通,我們可以去考察 \(f(x)\) 在樹上的分布情況:他的最大值在直徑端點取得,最小值在直徑中心取得,並且變化較為均勻。可以以直徑為根建樹,那么樹的從上到下 \(f(x)\) 一定是遞增的。
實際上沒有必要以直徑為根建樹,以最小的 \(f(x)\) 建樹即可,這樣還是能保證從上到下 \(f(x)\) 遞增。然后我們類似地枚舉最小值,剩下的點只能在子樹中選取了,好的實現方式是每個點往上打樹上差分標記(其實就是切換了主體)
時間復雜度 \(O(nq)\),我個人的想法稍復雜一點,但很接近正解了。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
#define int long long
const int inf = 1e18;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,x,ans,rt,tot,f[M],da[M],db[M],d[M],c[M];
vector<int> v,p;
struct edge{int v,c,next;}e[M<<1];
void dfs1(int u,int fa,int *d)
{
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
d[v]=d[u]+e[i].c;
dfs1(v,u,d);
}
}
void dfs2(int u,int fa)
{
v.push_back(d[u]);p.push_back(u);
c[p[lower_bound(v.begin(),v.end(),d[u]-x)-
v.begin()-1]]--;c[u]=1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
dfs2(v,u);
c[u]+=c[v];
}
ans=max(ans,c[u]);
v.pop_back();p.pop_back();
}
signed main()
{
n=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read(),c=read();
e[++tot]=edge{u,c,f[v]},f[v]=tot;
e[++tot]=edge{v,c,f[u]},f[u]=tot;
}
dfs1(1,0,da);
for(int i=1;i<=n;i++) if(da[rt]<da[i]) rt=i;
memset(da,0,sizeof da);
dfs1(rt,0,da);
for(int i=1;i<=n;i++) if(da[rt]<da[i]) rt=i;
dfs1(rt,0,db);rt=1;
for(int i=1;i<=n;i++)
{
d[i]=max(da[i],db[i]);
if(d[rt]>d[i]) rt=i;
}
m=read();
v.push_back(-inf);p.push_back(0);
while(m--)
{
x=read();ans=0;
for(int i=0;i<=n;i++) c[i]=0;
dfs2(rt,0);
printf("%lld\n",ans);
}
}
CF521D Shop
題目描述
解法
我直接手切所以簡記一下,最后發現做法和 \(\tt jiangly\) 聚聚一模一樣。
操作很多樣,首先我們可以做出一些簡單的觀察:
- 如果知道了要選哪些操作,那么操作的順序一定是
賦值->加法->乘法
- 賦值操作只需要保留每個位置最大的即可。
- 因為賦值操作最先進行,所以可以看成加法。
- 因為加法一定是從大到小進行,所以可以看成乘法,也就是在原來的基礎上增加了幾倍。
- 因為答案的特殊形式,所以一定是乘上的值越大越好。
根據上面的觀察,就不難設計出先把賦值轉化成加法,然后把加法按照權值排序,計算轉化成乘法之后的數乘。然后直接按權值從小到大選取乘法,但是注意這不是最終的順序,還要按照種類排序,時間復雜度 \(O(n\log n)\)
總結
化歸的思想,把不同操作統一成同種形式更有利於分析。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
#define db double
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,a[M];pair<int,int> mx[M];
struct node{db v;int id,ty;};vector<node> v,ad[M];
bool cmp1(node a,node b) {return a.v>b.v;}
bool cmp2(node a,node b) {return a.ty<b.ty;}
signed main()
{
n=read();m=read();k=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++)
{
int op=read(),x=read(),y=read();
if(op==1) mx[x]=max(mx[x],make_pair(y,i));
if(op==2) ad[x].pb(node{y,i,2});
if(op==3) v.pb(node{y,i,3});
}
for(int i=1;i<=n;i++)
{
if(mx[i].first>a[i])//trans cover to add
ad[i].pb
(node{mx[i].first-a[i],mx[i].second,1});
sort(ad[i].begin(),ad[i].end(),cmp1);
db sum=a[i];
for(auto x:ad[i])//trans add to mul
{
v.pb(node{(sum+x.v)/sum,x.id,x.ty});
sum+=x.v;
}
}
sort(v.begin(),v.end(),cmp1);
while(v.size()>k) v.pop_back();
sort(v.begin(),v.end(),cmp2);
printf("%d\n",(int)v.size());
for(auto x:v) printf("%d ",x.id);
puts("");
}