bzoj4086 [Sdoi2015]travel(分类讨论+容斥原理)

题目链接

分析:
第一眼看到这道题的时候,以为是一道状压dp(毕竟k的范文很小啊)
于是设计了许多奇怪的状态,但是由于n<=1000,状压是肯定hold不住的

k的范围小是有作用的,我们考虑分类讨论

  • K=2:显然答案就是原连通矩阵
  • K=3:枚举起点i,从起点开始枚举两条前后连接的边,维护连通性
    复杂度:O(m^2)
void solve3()
{
    for (int i=1;i<=n;i++)
        for (int j=st[i];j;j=way[j].nxt)
        {
            int nxt=way[j].y;
            for (int k=st[nxt];k;k=way[k].nxt)
                if (i!=way[k].y) ans[i][way[k].y]=ans[way[k].y][i]=1;
        }
}
  • K=4:一开始我枚举了中间的这条边,之后分别枚举两端点,这样的时间复杂度是O(m*n^2)
    显然我们可以do better:枚举两个端点x和y,从两个端点枚举ta们连接的边(x,u)(y,w),判断u和w是否连通
    复杂度:O(m^2)
void solve4()
{
    for (int x=1;x<=n;x++)
        for (int y=x+1;y<=n;y++)
            for (int i=st[x];i;i=way[i].nxt)
                for (int j=st[y];j;j=way[j].nxt)
                if (way[i].y!=way[j].y&&way[i].y!=y&&way[j].y!=x&&mp[way[i].y][way[j].y])
                    ans[x][y]=ans[y][x]=1;
}
  • K=5:这种情况就涉及到了简单的容斥
    bzoj4086 [Sdoi2015]travel(分类讨论+容斥原理)_第1张图片
    如图,红线标注的就是一个合法的路径,因此(x,y)是合法点对
    我们要怎么计算这种情况呢?
    首先枚举p,q两点,从p点扩展出一条边(p,z),如果z和q连通,那么(p,z,q)就是一个合法的三元组
    cnt[i] 表示将(p,q)作为三元组的两端点时,点i是否可以作为三元组的中间点
    sum 是针对枚举的(p,q)而言有多少合法的中间点
for (int x=1;x<=n;x++)
        for (int y=x+1;y<=n;y++) 
        {
            for (int i=1;i<=n;i++) cnt[i]=0; sum=0;
            for (int i=st[x];i;i=way[i].nxt)
                if (way[i].y!=y&&mp[way[i].y][y]) cnt[way[i].y]++,sum++;  //中间点
         }

在计算的时候,我们从p,q扩展两条边出来,枚举x和y
但是有可能我们枚举的x和y是中间点
没关系,我们用容斥原理

sumcnt[x]cnt[y] 大于 0 的时候,

说明即使x(或y)是中间点,仍有另一个中间点(如图中的u)可以使得x—>y经过5个点
因此此时点对(x,y)是合法的

  • K=6:思路与K=5的情况大概相同
    bzoj4086 [Sdoi2015]travel(分类讨论+容斥原理)_第2张图片
    记链为x—>u—>p—>q—>v—>y
    枚举u,v记下所有可能的二元组(p,q),记不同二元组的总数为sum,
    每个点在二元组中出现的次数为cnt[i],
for (int u=1;u<=n;u++)
        for (int w=u+1;w<=n;w++)
        {
            for (int i=1;i<=n;i++) cnt[i]=0;  sum=0;
            for (int i=st[u];i;i=way[i].nxt) if (way[i].y!=w)
                for (int j=st[w];j;j=way[j].nxt) if (way[j].y!=u&&way[j].y!=way[i].y&&mp[way[i].y][way[j].y])
                    cnt[way[i].y]++,cnt[way[j].y]++,sum++;    //中间点对 
        }

然后从u,v扩展出两条边(枚举x,y)

sumcnt[x]cnt[y]+[xyxy]>0

(x,y)为可行点对

简单解释一下:由容斥原理可得,路径条数等于sum减去x在二元组出现的次数,减去y在二元组出现的次数,最后加上x和y在同一个二元组出现的次数

  • K=7:依旧是考虑容斥
    bzoj4086 [Sdoi2015]travel(分类讨论+容斥原理)_第3张图片
    记链为x—>u—>p—>z—>q—>v—>y
    先记下对所有(p,q)中z的集合
    然后枚举u,v,从这两个点扩展出点对(p,q),对中间的所有可行的三元组,记录总数为 sum
    接下来的操作我们是针对三元组进行的容斥
    记每个点在三元组中出现次数为cnt[i]
    记i,j都在两端的次数为cnt1[i][j]
    记i在三元组中点位置,j在两端的次数为cnt2[i][j]

枚举x,y

sumcnt[x]cnt[y]+cnt2[x][y]+cnt2[y][x]+cnt1[x][y]>0

则(x,y)合法

简单解释一下:
最后枚举的两个点(x,y)一定不会和u,v重合
因此路径条数等于sum减去x在三元组出现的次数,减去y在三元组出现的次数,
加上(x,y)和(p,q)重合的情况(cnt1[x][y])
加上(x,y)中一个与三元组的中点重合的情况(cnt2[x][y],cnt2[y][x])

K<7的情况可以在O(m^2)的时间内完成,K=7的情况有点难估计

tip

时限80s啊。。。
这里写图片描述

代码专治手残,眼瞎等疾病
注意初始化

遇到数据范围比较小的时候,我们可以考虑分类讨论(部分分做法?)

#include
#include
#include
#include
#define ll long long

using namespace std;

const int N=1003;
int st[N],tot,n,m,K;
bool ans[N][N],mp[N][N];
struct node{
    int x,y,nxt;
};
node way[N*10];

void add(int u,int w)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}

int cnt[N],sum,tt;

void solve3()
{
    for (int i=1;i<=n;i++)
        for (int j=st[i];j;j=way[j].nxt)
        {
            int nxt=way[j].y;
            for (int k=st[nxt];k;k=way[k].nxt)
                if (i!=way[k].y) ans[i][way[k].y]=ans[way[k].y][i]=1;
        }
}

void solve4()
{
    for (int x=1;x<=n;x++)
        for (int y=x+1;y<=n;y++)
            for (int i=st[x];i;i=way[i].nxt)
                for (int j=st[y];j;j=way[j].nxt)
                if (way[i].y!=way[j].y&&way[i].y!=y&&way[j].y!=x&&mp[way[i].y][way[j].y])
                    ans[x][y]=ans[y][x]=1;
}

void solve5()
{ 
    for (int x=1;x<=n;x++)
        for (int y=x+1;y<=n;y++) 
        {
            for (int i=1;i<=n;i++) cnt[i]=0; sum=0;
            for (int i=st[x];i;i=way[i].nxt)
                if (way[i].y!=y&&mp[way[i].y][y]) cnt[way[i].y]++,sum++;  //中间点

            for (int i=st[x];i;i=way[i].nxt) if (way[i].y!=y)
                for (int j=st[y];j;j=way[j].nxt) if (way[j].y!=x&&way[i].y!=way[j].y)
                    ans[way[i].y][way[j].y] = ( ans[way[j].y][way[i].y] |= (sum-cnt[way[i].y]-cnt[way[j].y]>0) );
        }

}

void solve6()
{
    for (int u=1;u<=n;u++)
        for (int w=u+1;w<=n;w++)
        {
            for (int i=1;i<=n;i++) cnt[i]=0;  sum=0;
            for (int i=st[u];i;i=way[i].nxt) if (way[i].y!=w)
                for (int j=st[w];j;j=way[j].nxt) if (way[j].y!=u&&way[j].y!=way[i].y&&mp[way[i].y][way[j].y])
                    cnt[way[i].y]++,cnt[way[j].y]++,sum++;    //中间点对 

            for (int i=st[u];i;i=way[i].nxt) if (way[i].y!=w)
                for (int j=st[w];j;j=way[j].nxt) if (way[j].y!=u&&way[j].y!=way[i].y)
                    ans[way[i].y][way[j].y] = ( ans[way[j].y][way[i].y] |= (sum-cnt[way[i].y]-cnt[way[j].y]+mp[way[i].y][way[j].y]>0) );
        }
}

vector<int> c[N][N];
int cnt2[N][N],cnt1[N][N],num[N][N],sz[N][N];

void solve7()
{
    for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) c[i][j].clear(),num[i][j]=sz[i][j]=0;
    for (int x=1;x<=n;x++)            //三元组 
        for (int i=st[x];i;i=way[i].nxt)
            for (int j=st[way[i].y];j;j=way[j].nxt) if (way[j].y!=x)
                c[x][way[j].y].push_back(way[i].y);  

    //tot=0;这个变量比较迷,实际上就是一个标志作用,没有计数作用  

    for (int p=1;p<=n;p++)
        for (int q=p+1;q<=n;q++)
        {
            for (int i=1;i<=n;i++) cnt[i]=0;   ++tot;  sum=0;
            for (int i=st[p];i;i=way[i].nxt) if (way[i].y!=q)
                for (int j=st[q];j;j=way[j].nxt) if (way[j].y!=p&&way[j].y!=way[i].y)
                    for (int k=0;k//三元组中间点 
                    {
                        int t=c[way[i].y][way[j].y][k];
                        if (t==p||t==q) continue;                     //到现在为止已经形成了一个五元组 
                        //way[i].y  way[j].y  k 组成一个三元组 
                        if (num[way[i].y][way[j].y]==tot) ++cnt1[way[i].y][way[j].y]; //都在两端     
                        else num[way[i].y][way[j].y]=tot,cnt1[way[i].y][way[j].y]=1;

                        if (sz[way[i].y][t]==tot) ++cnt2[way[i].y][t];   //一个在一端,一个在三元组内
                        else sz[way[i].y][t]=tot,cnt2[way[i].y][t]=1;

                        if (sz[way[j].y][t]==tot) ++cnt2[way[j].y][t];
                        else sz[way[j].y][t]=tot,cnt2[way[j].y][t]=1;

                        ++cnt[way[i].y]; 
                        ++cnt[way[j].y];
                        ++cnt[t];
                        ++sum;
                    }

            for (int i=st[p];i;i=way[i].nxt) if (way[i].y!=q)
                for (int j=st[q];j;j=way[j].nxt) if (way[j].y!=p&&way[j].y!=way[i].y)
                    ans[way[i].y][way[j].y]=( ans[way[j].y][way[i].y] |=
                    ( sum - cnt[way[i].y] - cnt[way[j].y] 
                    + (num[way[i].y][way[j].y]==tot? cnt1[way[i].y][way[j].y]:0)
                    + (sz[way[i].y][way[j].y]==tot? cnt2[way[i].y][way[j].y]:0)
                    + (sz[way[j].y][way[i].y]==tot? cnt2[way[j].y][way[i].y]:0) >0 ) );
        }
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(ans,0,sizeof(ans));
        memset(mp,0,sizeof(mp));
        memset(st,0,sizeof(st)); tot=0;

        scanf("%d%d%d",&n,&m,&K);
        for (int i=1;i<=m;i++)
        {
            int u,w;
            scanf("%d%d",&u,&w);
            if (!mp[u][w]) add(u,w);
            mp[u][w]=1; mp[w][u]=1;
        }

        if (K==2) memcpy(ans,mp,sizeof(ans));
        else if (K==3) solve3();
        else if (K==4) solve4();
        else if (K==5) solve5();
        else if (K==6) solve6();
        else if (K==7) solve7();

        for (int i=1;i<=n;i++){
            for (int j=1;j<=n;j++)
                printf("%c",(ans[i][j])? 'Y':'N');
            printf("\n");
        }
    } 
    return 0;
} 

你可能感兴趣的:(组合数学,省选)