CF-11D.A Simple Task【思维+状压dp】
思路
\(dp[i][j]:\)表示状态为\(i\),\(1\)表示已走到这个点,\(0\)表示未走到这个点,此时站在\(j\)点上的所有方案数。
发现我们在计算的时候可能会出现重复的情况,例如在计算\(1,2,3\)这个环时先按照顺序计算了\(1,2,3\),又计算了\(2,3,1\),这样可以发现是重复的情况。所以需要dp的限制就变成了:状态为\(i\),此时站在点\(j\)上,且这种状态的起点为状态\(i\)中的第一个为1的值。
接下来考虑转移,先考虑不合法的状态, 假设但前点在\(j\),接下来要走的点为\(k\),状态\(i\)的起点为\(s\),如果发现\(k<s\),那么就说明不能走到起点之前。
考虑合法状态:
假设已经在\(j\)点上,考虑\(j\)能到的点。如果\(k\)点没有到过,那么转移方程即为\(dp[i|(1<<k)]+=dp[i][j]\)。
考虑到过已经到过的点:此时\(j\)走过去的\(k\)点恰好是\(s\)点,那么就说明走成了一个环,就可以对答案产生贡献值\(res+=dp[i][j]\)。
如果走过去的\(k\)点不是\(s\)点,那么其产生的贡献会在\(s=k\)的那些状态中计算,不用考虑。
最后需要减去二元环的所有数即总数减边数\(res-=m\)。
因为我们计算的时候没有考虑顺时针或者逆时针顺序(例如\(1-2-3,1-3-2\)),所以对最后的答案还要除以2:\(res/=2\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, LL> PIL;
typedef pair<int, int> PII;
typedef pair<int, double> PID;
typedef unsigned long long ULL;
#define x first
#define y second
const int N = 500 + 10, M = 1e5 + 10;
const double PI = acos(-1.0);
const double eps = 1e-5;
const int mod = 1000000007;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
#define gcd __gcd
set<int> e[N];
LL dp[(1 << 19) + 10][20];
void solve() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
u--; v--;
e[u].insert(v);
e[v].insert(u);
}
LL res = 0;
for(int i = 0; i < n; i++) {
dp[1 << i][i] = 1;
}
for(int i = 1; i < (1 << n); i++) {
vector<int> vec;
int mx = 20;
for(int j = 0; j < n; j++) {
if((i >> j) & 1) {
vec.push_back(j);
mx = min(mx, j);
}
}
for(auto u : vec) {
for(auto v : e[u]) {
if(v < mx) continue;
if(!((1 << v) & i)) {
dp[i | (1 << v)][v] = dp[i | (1 << v)][v] + dp[i][u];
}
else {
if(v == mx) {
res += dp[i][u];
}
}
}
}
}
printf("%lld\n", (res - m) / 2);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif // ONLINE_JUDGE
solve();
return 0;
}