雅礼集训 Day5 T3 题 解题报告

题目背景

由于出题人赶时间所以没办法编故事来作为背景。

题目描述

一开始有\(n\)个苹果,\(m\)个人依次来吃苹果,第\(i\)个人会尝试吃\(u_i\)\(v_i\)号苹果,具体来说分三种情况。

• 1、两个苹果都还在,那么这个人将随便选一个苹果吃了。

• 2、只有一个苹果,那么这个人将吃掉这个苹果。

• 3、都不在了,这个人吃不到苹果就走了。

请问有多少对苹果\((i,j)\)\(i<j\))满足它们两个都幸存下来的概率\(>0\)

输入输出格式

输入格式

第一行两个数\(n,m\)

接下来\(m\)行,每行两个数\(u_i,v_i\)

输出格式

一个数表示答案。

数据范围

对于测试点 1 ∼ 5: \(n, m \le 20\)
对于测试点 5 ∼ 8:若把苹果看做点,人看做边,那么会形成一棵树。
对于测试点 9 ∼ 15: \(m \le 400\)
对于测试点 16 ∼ 25:无特殊限制。
对于所有的数据, \(n \le 400\); \(m \le 5 \times 10^4\)


牛 逼 题 !

通过手玩,我们可以发现一些简单但没有什么用的结论。

一些很自然的想法是,从第二个点给出的提示着手,或者是对每个点分开统计(毕竟点不多)之类的

但是直接出正解还是很困难,不接触过这种类型基本不可能吧。

考虑一个可存活的苹果集合\(S\)是否可以在\(k\)时间段合法

\(f_k(S)\)表示\(k\)时间段集合\(S\)是否合法,考虑它的递推式

\(k\)时间的边为\(E(u,v)\)

则当\(u \in S \ and \ v \in S\)时,\(f_k(S)=0\)
\(u \in S \ or \ v \in S\)时,\(f_k(S)=f_{k-1}(S \bigcup \{v\}) \ or \ f_{k-1}(S \bigcup \{u\})\)
\(u \notin S \ and \ v \notin S\)时,\(f_k(S)=f_{k-1}(S)\)

然而这有什么用呢,集合的状态如此多。

我们再次回到题目,要求我们统计点对是否合法,如果对点对\((u,v)\),它的\(f_m(\{u\})=1,f_m(\{v\})=1\)是否意味着它合法呢?

如果这个终止状态合法,那么包含这个终止状态的集合是否需要考虑呢?

第二个问题比较显然,贪心的去想,我们只需要讨论最后的一个最小的集合作为终止状态就一个不会更差了。

如果我们去思考第二个问题,可以发现它是不一定的,于是似乎又陷入了僵橘。

考虑终止状态唯一,想到每次期望\(DP\)都是倒着做的,这个题是否可以从终止状态往回推呢?

考虑回推
初始:\(f_m\{i\}=1\)
\(u \in S \ and \ v \in S\)时,此时推不回去了,说明没有情况可以推回去
\(u \in S \ or \ v \in S\)时,\(f_k(S \bigcup \{v\} \ or \ S \bigcup \{u\})=f_{k+1}(S)\)
\(u \notin S \ and \ v \notin S\)时,\(f_k(S)=f_{k+1}(S)\)

如果这个点在中途都回不去,说明它和谁都配不了

如果这个点搞完了所有的边并且得到了集合\(G\),则集合\(G\)包含的元素是得到它,一定要删去的点集(除了它自己)

于是这里就比较明了了,如果点对\((u,v)\)可以配对,当且仅当两者都可以推回去且推回去的\(G_u \bigcap G_v = \varnothing\)

这里这个集合用\(bitset\)维护一下

复杂度\(O(n^2m/ \omega)\)


Code:

#include <cstdio>
#include <bitset>
const int N=402;
std::bitset <N> g[N];
struct node{int u,v;}e[50010];
int n,m,f[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&e[i].u,&e[i].v);
    for(int i=1;i<=n;i++)
    {
        g[i][i]=1;
        for(int j=m;j;j--)
        {
            int u=e[j].u,v=e[j].v;
            if(g[i][u]&&g[i][v]) {f[i]=1;break;}
            if(g[i][u]||g[i][v]) g[i][u]=g[i][v]=1;
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        if(!f[i])
            for(int j=i+1;j<=n;j++)
                if(!f[j]&&!(g[i]&g[j]).count())
                    ++ans;
    printf("%d\n",ans);
    return 0;
}

2018.10.5

猜你喜欢

转载自www.cnblogs.com/ppprseter/p/9746002.html
今日推荐