NOIP提高组模拟赛加1
A. 哪一天她能重回我身边
学网络瘤学傻了,用费用瘤搞掉第一问就在想怎么搞方案,然后越想越偏。。。。。
不仅没有搞出来第二问,而且费用瘤的复杂度。。。。
总之就是挂的很惨。。。。
这题居然是个树形\(DP\)???
把背面的数向正面的数连边,翻一张卡相当于把边反向,我们要用最少次数让所有点入度小于等于\(1\),并且求出方案数
显然对每个联通块可以分开考虑
如果一个联通块\(n>m\)那么无论如何都无法满足要求
那么我们只需要考虑\(n==m\)和\(n-1==m\)两种情况
\(n-1==m\)这不是棵树吗
我们再看一眼目的“用最少次数让所有点入度小于等于\(1\)”
在树上就有且只有一个点入度为\(0\),我们令这个点为树根,然后换根\(DP\)就好了
\(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;
}