NOIP-DAY-2(2017 年全国青少年信息学奥林匹克竞赛黑龙江省选)

P3748 [六省联考2017]摧毁“树状图”

自从上次神刀手帮助蚯蚓国增添了上千万人口(蚯口?),蚯蚓国发展得越来越繁荣了!最近,他们在地下发现了一些神奇的纸张,经过仔细研究,居然是 D 国 X 市的超级计算机设计图纸!

这台计算机叫做 “树状图”,由 nnn 个计算节点与 n−1n - 1n1 条可以双向通信的网线连接而成,所有计算节点用不超过 nnn 的正整数编号。顾名思义,这形成了一棵树的结构。

蚯蚓国王已在图纸上掌握了这棵树的完整信息,包括 nnn 的值与 n−1n - 1n1 条网线的连接信息。于是蚯蚓国王决定,派出蚯蚓国最强大的两个黑客,小 P 和小 H,入侵 “树状图”,尽可能地摧毁它。

小 P 和小 H 精通世界上最好的编程语言,经过一番商量后,他们决定依次采取如 下的步骤:

  • 小 P 选择某个计算节点,作为他入侵的起始点,并在该节点上添加一个 P 标记。
  • 重复以下操作若干次(可以是 000 次):小 H 选择某个计算节点,作为她入侵的起始点,并在该节点上添加一个 H* 标记。
    • 小 P 从他当前所在的计算节点出发,选择一条没有被标记过的网线,入侵到该网线的另一端的计算节点,并在路过的网线与目的计算节点上均添加一个 P 标记。
  • 重复以下操作若干次(可以是 000 次):删除所有被标记过的计算节点和网线。
    • 小 H 从她当前所在的计算节点出发,选择一条没有被标记过的网线,入侵到该网线的另一端的计算节点,并在路过的网线与目的计算节点上均添加一个 H 标记。(注意,小 H 不能经过带有 P 标记的网线,但是可以经过带有 P 标记的计算节点)
  • 对于剩下的每条网线,如果其一端或两端的计算节点在上一步被删除了,则也删除这条网线。

经过以上操作后,“树状图” 会被断开,剩下若干个(可能是 000 个)连通块。为了达到摧毁的目的,蚯蚓国王希望,连通块的个数越多越好。于是他找到了你,希望你能帮他计算这个最多的个数。

小 P 和小 H 非常心急,在你计算方案之前,他们可能就已经算好了最优方案或最优方案的一部分。你能得到一个值 xxx:

  • x=0x = 0x=0,则说明小 P 和小 H 没有算好最优方案,你需要确定他们两个的入侵路线。
  • x=1x = 1x=1,则说明小 P 已经算好了某种两人合作的最优方案中,他的入侵路线。他将选择初始点 p0p_0p0,并沿着网线一路入侵到了目标点 p1p_1p1,并且他不会再沿着网线入侵;你只需要确定小 H 的入侵路线。
  • x=2x = 2x=2,则说明小 P 和小 H 算好了一种两人合作的最优方案,小 P 从点 p0p_0p0 入侵到了 p1p_1p1 并停下,小 H 从点 h0h_0h0 入侵到了 h1h_1h1 并停下。此时你不需要指挥他们入侵了,只需要计算最后两步删除计算节点与网线后,剩下的连通块个数即可。

输入格式

每个输入文件包含多个输入数据。输入文件的第一行为两个整数 TTT 和 xxx,TTT 表示该文件包含的输入数据个数,xxx 的含义见上述。(同一个输入文件的所有数据的 x 都是相同的。)

接下来依次输入每个数据。

输出格式

对于每个数据,输出一行,表示在给定条件下,剩下连通块的最大个数。

思路:

首先因为整体的图形是一棵树,所以当一条边被断掉则连通块会++,发现这个性质我们可以将所有的性质全部归于边上,但显然边并没有点好统计,固将所有的贡献统计在点上,发现所有的点具有的形态有且只有七种:

  1.子树中有一条链,并且该链不经过该子树根节点

  2.子树中有两条链,并且该两条链不经过该子树根节点

  3.子树中有一条链, 并且该链有一个端点在根节点

  4.子树中有一条链,并且该链在链中

  5.子树中有两条链,并且有一或者三个个端点在根节点

  6.子树中有两条链,并且有二或者四个端点在根节点上//建议去看洛谷的第三篇题解有图形具体的解释

  7.该节点跟链没有半点关系

但是跟答案有关的有且仅有前6种情况,具体的情况解释在代码注释中

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct hp
{
    int next,to;
}edge[maxn<<1];
int tot=0,head[maxn];
void add_edge(int u,int v)
{
    tot++;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot;
}
int p,pp,p1,p2;
int f1[maxn],f2[maxn],f3[maxn],f4[maxn],f5[maxn],f6[maxn];//能最多形成的连通块个数
void dfs(int x,int fa)
{
    int size=0;
    for(int i=head[x];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa)continue;
        dfs(v,x);
        int tmp1=f1[x],tmp2=f2[x],tmp3=f3[x],tmp4=f4[x],tmp5=f5[x],tmp6=f6[x];
        //由下向上更新
        f1[x]=max(f1[x],max(f1[v],max(f3[v],f4[v])+1));//删去子树中的一条路径增加的最多的连通块f1保证将根节点与子树之间相分开,f3,f4表示
        f2[x]=max(f2[x],max(f2[v],max(f5[v],f6[v])+1));//表示经过子树中含有两条链并且不经过该根节点的最大连通块数量,删去子树中的两条路径增加的最多的连通块总数,更新方式与1中同理
        f2[x]=max(f2[x],tmp1+max(f3[v],f4[v]));//同时注意子树中的两条路径可以通过一的方式拼凑出来
        f3[x]=max(f3[x]+1,f3[v]+size);//size表示之前的儿子节点,可以自己画一下图就能明白为什么统计之前的儿子节点个数,f3表示删去子树中的一条路径以及子树向外有路径一条一字型的路径
        //size也表示的之前的儿子全f1状态(将所有的信息全部归于更小的节点中去,
        f5[x]=max(f5[x]+1,max(f5[v]+size,max(tmp3+f1[v],max(tmp3+f4[v],f3[v]+tmp4))));

    //f5表示在子树中有两条路径并且有一条路径的端点在此点上而且此点又位于另外一条路径的中间,可以用f3[v]以及f4[x]拼凑出来以此类推
        //f5则表示删除子树中的两条边且子树的三叉戟边/或者1的状态后面则表示各种情况的拼凑
        f4[x]=max(f4[x]+1,tmp3+f3[v]);//表示子树中删除掉一条边根向外一条v字型的边的情况能不能向外延展
        f6[x]=max(f6[x]+1,max(f4[v]+tmp4,max(tmp5+f3[v],max(tmp3+f5[v],tmp4+f4[v]))));//
        //5和6的区别仅仅在于是否向外延展的区别吧
        //并且显然向外的v字形路径是可以被合并在一起的
        size++;     
    }
    f3[x]=max(f3[x],size);
    return;
}
int main()
{
    freopen("treediagram.in","r",stdin);
    freopen("treediagram.out","w",stdout);
    int T,x;
    scanf("%d %d",&T,&x);
    for(int i=1;i<=T;i++)
    {
        int n;
        scanf("%d",&n);
        if(x==1){scanf("%d %d",&p,&pp);}
        else if(x==2){scanf("%d %d %d %d",&p,&pp,&p1,&p2);}
        for(int i=1;i<=n-1;i++)
        {
            int u,v;
            scanf("%d %d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        dfs(1,0);
        int ans=max(f2[1],max(f5[1],f6[1]));//表示删除各种情况子树内两条边的情况
        printf("%d\n",ans);
        tot=0;
        for(int i=1;i<=n;i++)head[i]=0;
        for(int i=1;i<=n;i++)f1[i]=f2[i]=f3[i]=f4[i]=f5[i]=f6[i]=0;
    }
    return 0;
}

P3750 [六省联考2017]分手是祝愿

B 君在玩一个游戏,这个游戏由 nnn 个灯和 nnn 个开关组成,给定这 nnn 个灯的初始状态,下标为从 111 到 nnn 的正整数。

每个灯有两个状态亮和灭,我们用 111 来表示这个灯是亮的,用 000 表示这个灯是灭的,游戏的目标是使所有灯都灭掉。

但是当操作第 iii 个开关时,所有编号为 iii 的约数(包括 111 和 iii)的灯的状态都会被改变,即从亮变成灭,或者是从灭变成亮。

B 君发现这个游戏很难,于是想到了这样的一个策略,每次等概率随机操作一个开关,直到所有灯都灭掉。

这个策略需要的操作次数很多,B 君想到这样的一个优化。如果当前局面,可以通过操作小于等于 kkk 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个策略显然小于等于 kkk 步)操作这些开关。

B 君想知道按照这个策略(也就是先随机操作,最后小于等于 kkk 步,使用操作次数最小的操作方法)的操作次数的期望。

这个期望可能很大,但是 B 君发现这个期望乘以 nnn 的阶乘一定是整数,所以他只需要知道这个整数对 100003100003100003 取模之后的结果。

输入格式

第一行两个整数 n,kn, kn,k。 接下来一行 nnn 个整数,每个整数是 000 或者 111,其中第 iii 个整数表示第 iii 个灯的初始情况。

输出格式

输出一行,为操作次数的期望乘以 nnn 的阶乘对 100003100003100003 取模之后的结果。

输入输出样例

输入 #1
4 0
0 0 1 1
输出 #1
512
输入 #2
5 0
1 0 1 1 1
输出 #2
5120

说明/提示

对于 0%0\%0% 的测试点,和样例一模一样;
对于另外 30%30\%30% 的测试点,n≤10n \leq 10n10;
对于另外 20%20\%20% 的测试点,n≤100n \leq 100n100;
对于另外 30%30\%30% 的测试点,n≤1000n \leq 1000n1000;
对于 100%100\%100% 的测试点,1≤n≤100000,0≤k≤n1 \leq n \leq 100000, 0 \leq k \leq n1n100000,0kn;
对于以上每部分测试点,均有一半的数据满足 k=nk = nk=n。

思路:显而易见的期望dp题,并且每个开关只分亮与不亮两种情况,那我们就可以从大到小枚举一遍求出必须要按的最小的个数,可以用筛法解决

  难点在于怎么推出柿子:

  用dp表示从需要按下i个开关到需要按下i-1个开关所需要的期望次数

  显然有dp[i]=i/n(按到达成目标需要的i个按钮的几率)+((n-i)/n)*(f[i+1]+f[i]+1);(表示在按错了的概率以及之后需要再期望按dp[i+1]次按到只剩下dp[i]次按钮)

  略微化简亿下可得f[i]=(n+(n-i)*f[i+1])/i;那么我们就可以开始转移了

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=1e5+5;
const ll mod=100003;
int a[maxn];
ll quick_pow(ll x,ll p)
{
    ll an=1;
    ll po=x;
    while(p)
    {
        if(p%2)an=(an*po)%mod;
        po=(po*po)%mod;
        //cout<<x<<" "<<po<<" "<<an<<endl;
        p/=2;   
    }
    return an;
}
ll dp[maxn];
int main()
{
    freopen("trennen.in","r",stdin);
    freopen("trennen.out","w",stdout);
    int n,k;
    ll cnt=0;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)
    {scanf("%d",&a[i]);}
    for(int i=n;i>=1;i--)
    {
        if(a[i])
        {
            cnt++;
            for(int j=1;j*j<=i;j++)
            {
                if(i%j==0)
                {
                    a[j]^=1;
                    if((j*j)!=i)a[i/j]^=1;
                    //cout<<i<<" "<<j<<" "<<a[j]<<" "<<a[i/j]<<endl;
                }
            }
        }
    }
    //cout<<cnt<<endl;
    dp[n+1]=0;
    for(int i=n;i>=1;i--)
    {
        ll tmp=(ll)(n-i)*dp[i+1]%mod;
        tmp=(tmp+(ll)n)%mod;
        tmp=tmp*quick_pow(i,mod-2)%mod;
        dp[i]=tmp;
    }
    ll ans=0;
    if(cnt<=k)ans=cnt;
    else
    {
        for(int i=cnt;i>k;i--)ans=(ans+dp[i])%mod;//表示需要按的按钮最少为cnt个之后开始转移的
        ans=(ans+k)%mod;
    }
    //cout<<ans<<endl;
    for(int i=1;i<=n;i++)ans=(ans*i)%mod;
    printf("%lld\n",ans);
    return 0;
 }

 

posted @ 2021-09-16 20:32  -白翎-windrise  阅读(120)  评论(1)    收藏  举报