题解 P3213 【[HNOI2011]勾股定理】
bzy369258147 · · 题解
本文旨在探讨关于该题网上流传的唯一一个玄学题解的正确性证明,以及关于该题图的性质的分析.
实名反对楼上的题解关于HNOI2011出题人的评价(大雾
以及这题是一道类似 HNOI2018毒瘤 的 好(毒瘤) 题.
也是一个本质上的 提交答案题.
观察题意不难发现这题是给定图求独立集的个数.
然而对一般图而言这是NP的,所以我们猜想这是一个特殊图.
那它是不是:
链? (X)
树? (X)
基环树? (X)
仙人掌? (?)
二分图? (X)
正则图? (X)
弦图? (X)
那是不是出题人就是毒瘤,就是拿一个NP问题当普通题出出来? (X)
通过观察,我们发现这个图,它,至少,是,一个,平面图...
然而并没有什么X用23333
这是我在求出(1 - 2e5)所有边再拓扑排序以后剩下的图的样子:
把里边相交的环展到大环外面就是一个平面图了.
这是我在求出(2e4 - 1e6)所有边再拓扑排序以后剩下的图的样子:
我们发现它甚至是一个沙漠图(仙人掌森林).
也就是说出题人故意安排的部分分其实是有意义的。
具体分3档
30pt(1) : 树形DP
70pt(1 + 3) : 仙人掌DP
100pt(1 + 2 + 3) : 正解.
通过观察发现,原图所有的联通子图中最多比树多3条边,所以这题直接变成了毒瘤的弱化版,所以所谓的玄学做法(毒瘤的部分分做法)自然能过啦.
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int PW2[1000005];
const int maxN = 1e6;
int n;
int num[1000005];
vector<int> to[1000005];
bool vis[1000005];
bool ins[1000005];
int sat[1000005];
vector<int> QE;
void dfs_init( int x, int f ) {
vis[x] = true;
for( auto N : to[x] ) if( N ^ f ) {
if( !vis[N] ) dfs_init( N, x );
else {
if( !ins[x] ) QE.push_back( x );
if( !ins[N] ) QE.push_back( N );
ins[x] = ins[N] = true;
}
}
}
int dp[1000005][2];
int des[1000005];
int pnt = 0;
int dfs_dp( int x ) {
dp[x][0] = 1; dp[x][1] = PW2[ num[x] ] - 1; des[x] = pnt;
for( auto N : to[x] ) if( des[N] ^ pnt ) {
dp[x][0] = 1ll * dp[x][0] * dfs_dp( N ) % mod;
dp[x][1] = 1ll * dp[x][1] * dp[N][0] % mod;
}
if( sat[x] == 1 ) dp[x][0] = 0;
if( sat[x] == -1 ) dp[x][1] = 0;
return ( dp[x][0] + dp[x][1] ) % mod;
}
bool check() {
for( auto P : QE ) for( auto N : to[P] )
{ if( sat[P] == 1 and sat[N] == 1 ) return false; }
return true;
}
int query(int x) {
QE.clear(); dfs_init( x, x );
int ans = 0;
int len = 1 << QE.size();
for( int i = 0; i < len; i ++ ) {
for( int j = 0; j < QE.size(); j ++ )
{ sat[ QE[j] ] = (i & (1 << j)) ? 1 : -1; }
if( check() ) pnt ++, ( ans += dfs_dp( x ) ) %= mod;
}
for( int i = 0; i < QE.size(); i ++ ) sat[ QE[i] ] = 0;
return ans;
}
int main(){
int n; cin >> n; PW2[0] = 1;
for(int i = 1;i <= n;i ++) PW2[i] = PW2[i - 1] * 2 % mod;
for(int i = 1;i <= n;i ++) { int x; cin >> x; num[x] ++; }
for(int i = 1;i * i <= maxN;i ++) for(int j = i + 1;2 * i * j <= maxN;j ++) {
if( j * j > 2 * maxN ) break;
int x = j * j - i * i, y = 2 * i * j;
if( x > maxN or y > maxN ) continue;
if( !num[x] or !num[y] or __gcd( x, y ) != 1 ) continue;
to[x].push_back(y); to[y].push_back(x);
}
int ans = 1;
for(int i = 1;i <= maxN;i ++) if( num[i] and !vis[i] )
{ ans = 1ll * ans * query(i) % mod; }
cout << ( ans - 1 + mod ) % mod;
return 0;
}
拓扑排序的代码:
#include<bits/stdc++.h>
using namespace std;
int N = 200000;
int M = 0;
int deg[1000005];
vector<int> to[1000005];
int main(){
for(int i = 2;i <= N;i ++) {
for(int j = 1;j <= i;j ++) {
if( 1ll * 2 * i * j > N ) break;
if( 1ll * i * i - 1ll * j * j > N ) continue;
if( __gcd( i, j ) > 1 ) continue;
long long u = i * i - j * j;
long long v = 2 * i * j;
if( __gcd( u, v ) > 1 ) continue;
if( u > v ) swap( u, v );
if( u < M or v < M ) continue;
deg[u] ++; deg[v] ++; to[u].push_back(v); to[v].push_back(u);
//printf( "%d %d\n", u, v );
}
}
queue<int> Q;
for(int i = 1;i <= N;i ++) if( deg[i] == 1 ) Q.push(i);
while( !Q.empty() ) {
int x = Q.front(); Q.pop();
for( auto N : to[x] ) {
deg[N] --;
if( deg[N] == 1 ) Q.push( N );
}
}
for(int i = 1;i <= N;i ++) if( deg[i] > 1 )for( auto E : to[i] ) if( deg[E] > 1 ) printf( "%d %d\n", i, E );
//for(int i = 1;i <= N;i ++) if( deg[i] > 1 ) printf( "%d %d\n", i, deg[i] );
return 0;
}