NOIP提高組模擬賽加1
A. 哪一天她能重回我身邊
學網路瘤學傻了,用費用瘤搞掉第一問就在想怎麼搞方案,然後越想越偏。。。。。
不僅沒有搞出來第二問,而且費用瘤的複雜度。。。。
總之就是掛的很慘。。。。
這題居然是個樹形\(DP\)???
把背面的數向正面的數連邊,翻一張卡相當於把邊反向,我們要用最少次數讓所有點入度小於等於\(1\),並且求出方案數
顯然對每個聯通塊可以分開考慮
如果一個聯通塊\(n>m\)那麼無論如何都無法滿足要求
那麼我們只需要考慮\(n==m\)和\(n-1==m\)兩種情況
\(n-1==m\)這不是棵樹嗎
我們再看一眼目的“用最少次數讓所有點入度小於等於\(1\)”
在樹上就有且只有一個點入度為\(0\)
\(n==m\)基環樹,環上要麼順時針要麼逆時針,其實只有兩種情況,隨便找個環上的邊斷開,分別以兩邊為根\(DFS\)一次即可
最小次數是所有聯通塊最小次數和,方案數是所有聯通塊方案數乘起來。
基環樹那裡有點小細節。。
code
#include<cstdio> #include<cstring> using namespace std; const int mod=998244353; const int maxn=200005; const int inf=0x3f; int n,head[maxn],tot; struct edge{int net,to,val;}e[maxn<<1|1]; bool vis[maxn]; void add(int u,int v,int w){ e[++tot].net=head[u]; head[u]=tot; e[tot].to=v; e[tot].val=w; } void link(int u,int v){ add(u,v,1);add(v,u,0); } int cnt1,cnt2; void dfs(int x){ vis[x]=1;++cnt1; for(int i=head[x];i;i=e[i].net){ int v=e[i].to;++cnt2; if(vis[v])continue; dfs(v); } } int jh; void DFS(int x,int fa){ bool flag=0;vis[x]=1; for(int i=head[x];i;i=e[i].net){ int v=e[i].to; if(v==x)jh=i; if(v==fa){ if(flag)jh=i; else flag=1; }else{ if(vis[v])jh=i; else DFS(v,x); } } } int mi,cnt; void TD_1(int x,int fa,int s1,int s2){ for(int i=head[x];i;i=e[i].net){ int v=e[i].to; if(v==fa||i==s1||i==s2)continue; if(e[i].val==0)++mi; TD_1(v,x,s1,s2); } } int now; void TD_2(int x,int fa){ if(now<mi)mi=now,cnt=0; if(now==mi)++cnt; for(int i=head[x];i;i=e[i].net){ int v=e[i].to; if(v==fa)continue; if(e[i].val==1){ ++now;TD_2(v,x);--now; }else{ --now;TD_2(v,x);++now; } } } int main(){ int T;scanf("%d",&T); for(int ask=1;ask<=T;++ask){ tot=0;for(int i=1;i<=n+n;++i)head[i]=0; scanf("%d",&n); for(int i=1;i<=n;++i){ int u,v;scanf("%d%d",&u,&v);link(v,u); } bool flag=1; memset(vis,0,sizeof(vis)); for(int i=1;i<=n+n;++i) if(!vis[i]){ cnt1=cnt2=0; dfs(i); cnt2/=2; if(cnt2>cnt1){flag=0;break;} } if(!flag)printf("-1 -1\n"); else{ long long ans2=1;int ans1=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=n+n;++i){ if(head[i]==0||vis[i])continue; jh=0;DFS(i,i); if(jh){ int u=e[jh].to,v,hj; if(jh%2)hj=jh+1;else hj=jh-1; v=e[hj].to; if(e[jh].val==0){u^=v;v^=u;u^=v;} mi=0;TD_1(u,u,jh,hj);int r1=mi; mi=1;TD_1(v,v,jh,hj); if(r1==mi)ans2=ans2*2%mod; ans1+=r1>mi?mi:r1; }else{ mi=0;TD_1(i,i,0,0); now=mi;cnt=0;TD_2(i,i); ans2=ans2*cnt%mod; ans1=ans1+mi; } } printf("%d %lld\n",ans1,ans2); } } return 0; }
單
考場推柿子,亂搞半天激動地發現解出了\(S\),趕快實現,,,然後浮點數例外??
仔細觀察,大概就是搞了半天整出的柿子是\(0*S=0\),解個毛線。。。
首先知道\(a\)求\(b\),一個簡單的樹形\(DP\)
令\(S_i\)表示以\(i\)為根的子樹所有權值的和,整棵樹的根為\(1\)
從\(DP\)中我們可以得到\(b_i=b_{fa}-S_i-S_i+S_1\),這個顯然是反推的關鍵
我們可以得到
\(b_i-b_{fa}=S_1-2*S_i\)
錯誤搞法\(0=0\)就不說了。。
正解考慮
\(\sum_{i=2}^{n}b_i-b_{i->fa}=(n-1)*S_1-2*\sum_{i=2}^{n}S_i\)
然後我們再想想當初是怎麼求\(b_1\)的,你會發現\(b_1=\sum_{i=2}^{n}S_i\)
那麼\(\sum_{i=2}^{n}b_i-b_{i->fa}=(n-1)*S_1-2*b_1\)
這裡可以解出\(S_1\),然後剩下的就非常簡單了
code
#include<cstdio>
#include<cstring>
using namespace std;
#define int long long
const int maxn=100000;
int head[maxn],tot,n;
struct edge{int to,net;}e[maxn<<1|1];
void add(int u,int v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
int re[maxn],pr[maxn],s[maxn],dep[maxn],dt[maxn];
void DFS(int x,int fa){
if(x!=1)dt[x]=re[x]-re[fa];
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
DFS(v,x);
}
}
void DP(int x,int fa){
pr[x]=s[x];
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
DP(v,x);
pr[x]-=s[v];
}
}
void worka(){
for(int i=1;i<=n;++i)pr[i]=0;
for(int i=1;i<=n;++i)dt[i]=0;
for(int i=1;i<=n;++i)s[i]=0;
DFS(1,1);
for(int i=2;i<=n;++i)s[1]+=dt[i];
s[1]+=re[1]+re[1];
s[1]/=(n-1);
for(int i=2;i<=n;++i)s[i]=(s[1]-dt[i])/2;
DP(1,1);
}
void dfs(int x,int fa){
s[x]+=re[x];
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
dep[v]=dep[x]+1;dfs(v,x);
s[x]+=s[v];
}
}
void dp(int x,int fa){
if(x!=1)pr[x]=pr[fa]-s[x]-s[x]+s[1];
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
dp(v,x);
}
}
void workb(){
for(int i=1;i<=n;++i)s[i]=0;
dep[1]=0;dfs(1,1);
for(int i=1;i<=n;++i)pr[i]=0;
for(int i=1;i<=n;++i)pr[1]+=dep[i]*re[i];
dp(1,1);
}
signed main(){
int T;scanf("%lld",&T);
for(int ask=1;ask<=T;++ask){
scanf("%lld",&n);
for(int i=1;i<=n;++i)head[i]=0;tot=0;
for(int i=1;i<n;++i){
int u,v;scanf("%lld%lld",&u,&v);
add(u,v);add(v,u);
}
int type;scanf("%lld",&type);
for(int i=1;i<=n;++i)scanf("%lld",&re[i]);
if(type)worka();
else workb();
for(int i=1;i<=n;++i)printf("%lld ",pr[i]);printf("\n");
}
return 0;
}