bzoj 2169 连边——去重的思想

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2169

如果之前都去好重了,可以看作这次连的边只会和上一次连的边重复。

可以认为从上上次的状态到这次的状态,转移的过程对于上上次的每个状态来说都是把剩余位置所有连边的可能性遍历了恰好一遍!即,当前连了 i 条边,与上次连的边重复的数量就是 C(n,2)-(i-2)(n个点里选2个是一共有多少空位放边,上上次已经放了 i-2 条,这次与上次可以重复的位置有该式那么多个)。

关于同种方案因为连边顺序不同导致的重复计数,只要每次算好一条边之后除以 i 即可;意即对每一种方案,这次放的边可以在已经放的 i 条边中的任意一条,导致重复。不这样去重而最后除以所填边数的阶乘是不行的,因为自己的转移在由有重复的状态转移来的时候不成立。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005,mod=10007;
int n,m,t,dp[N][N];
bool deg[N];
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
int calc(int a){return (a*(a-1)>>1)%mod;}
int pw(int x,int k)
{
  int ret=1;while(k){if(k&1)ret=ret*x%mod;x=x*x%mod;k>>=1;}return ret;
}
int main()
{
  n=rdn(); m=rdn(); t=rdn();
  for(int i=1,u,v;i<=m;i++)
    {
      u=rdn(); v=rdn();
      deg[u]=!deg[u]; deg[v]=!deg[v];
    }
  int cnt=0;
  for(int i=1;i<=n;i++) cnt+=deg[i];
  dp[0][cnt]=1;
  for(int i=1;i<=t;i++)
    {
      int d=pw(i,mod-2);
      for(int j=0;j<=n;j++)
    {
      dp[i][j]=dp[i-1][j]*j%mod*(n-j)%mod;
      dp[i][j]=(dp[i][j]+dp[i-1][j+2]*calc(j+2))%mod;
      if(j>=2)
          dp[i][j]=(dp[i][j]+dp[i-1][j-2]*calc(n-j+2))%mod;
      if(i>1)dp[i][j]-=dp[i-2][j]*(calc(n)-(i-2))%mod;
      if(dp[i][j]<0)dp[i][j]+=mod;
      dp[i][j]=dp[i][j]*d%mod;
    }
    }
  printf("%d
",dp[t][0]%mod);
  return 0;
}
原文地址:https://www.cnblogs.com/Narh/p/9756338.html