@xiaoziyao
        
        2021-01-10T14:42:47.000000Z
        字数 2272
        阅读 1434
    解题报告
P7246 手势密码解题报告:
非常神仙的贪心题。
考虑使用一种“帮忙”思想:对于以为根的子树,我们将内部的点全部按要求覆盖,并从延伸出条路径来帮忙覆盖祖宗结点(某些路径也可以在结点止步)。
因此我们做一遍树形dp,对于每个结点,我们计算仅当前位置对答案的贡献,也就是说如果这里向上延伸出条路径,那么贡献就加;如果有两条路径在这里交汇或者一条路径在这里结束,那么贡献就减(本来要两条路径,现在用一条路径就可以覆盖了)。
设所有儿子组成了集合,那么很容易发现:
因此,在结点上理论上可以进行次匹配。
我们发现,在不增加子树内对答案的贡献的情况下且不让的覆盖次数超限的情况下,我们要减小匹配次数。因为我们减小匹配次数就可以让可以延伸出去的路径数更多,根据贪心的思想这样一定是更优的。
这里有一个问题,为什么要保证不增加子树内对答案的贡献呢?延伸出去不是可以减小祖先对答案的贡献吗?
我们发现对于某一条路径,且当前子树的根的覆盖次数还没有满,那么如果它在这里匹配,那么会减小的贡献;而如果延伸出去,只是有可能减小的贡献(有可能匹配不到路径)。因此我们发现在当前子树能减小贡献我们直接匹配一定是不劣的。
我们设当前根结点为,在处的匹配次数为,那么有(条在处匹配的路径,其余的路径都向上延伸,共条),也就是。
那么,我们当前子树最优匹配次数为。
知道了子树最优匹配次数,那么其他的都好算了。
(显然除去匹配的路径数对的覆盖数量,其余的每次覆盖都可以帮祖宗的忙)
而结点对答案的贡献为(所有路径能覆盖的次数为,因此结点还需要次覆盖)。
时间复杂度:。
#include<stdio.h>#define int long longconst int mod=1000000000,maxn=3000005,maxm=maxn<<1;int op,n,seed,e,ans;int a[maxn],start[maxn],to[maxm],then[maxm],v[maxn];inline int getseed(int x){seed=(1ll*seed*0x66CCF+19260817ll)%x+1;seed=(1ll*seed*0x77CCF+20060428ll)%x+1;seed=(1ll*seed*0x88CCF+12345678ll)%x+1;seed=(1ll*seed*0x33CCCCFF+10086001ll)%x+1;return seed;}inline int max(int a,int b){return a>b? a:b;}inline int min(int a,int b){return a<b? a:b;}inline void add(int x,int y){then[++e]=start[x],start[x]=e,to[e]=y;}void dfs(int x,int last){int sum=0,maxx=0,match;for(int i=start[x];i;i=then[i]){int y=to[i];if(y==last)continue;dfs(y,x),sum+=v[y],maxx=max(maxx,v[y]);}match=max(0,min(min(sum/2,sum-maxx),min(a[x],sum-a[x])));v[x]=a[x]-match;ans+=max(a[x]-(sum-match),0)-match;}signed main(){scanf("%lld",&op);if(op==1){scanf("%lld",&n);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);for(int i=1;i<n;i++){int x,y;scanf("%lld%lld",&x,&y);add(x,y),add(y,x);}}if(op==2){scanf("%lld%lld",&seed,&n);for(int i=1;i<=n;i++)a[i]=getseed(mod);for(int i=2;i<=n;i++){int x=getseed(i-1);add(x,i),add(i,x);}}dfs(1,0);printf("%lld\n",ans);return 0;}
