题目
题目链接:https://jzoj.net/senior/#main/show/3799
思路
如果一个数是完全平方数,当且仅当它分解质因数后,每一个质因数的质数均为偶数。
由于\(n\leq 90\),所以最多有\(24\)个质数。考虑状压。
所以我们预处理出\(prmS[x]\)表示数字\(x\)分解质因数后,指数为奇数的质因数集合。
设\(f[x][S]\)表示处理到\(DAG\)中的点\(x\),终点为\(x\)的所有路径中,有多少条指数为奇数的质数集合为\(S\)的路径。
由于是一张\(DAG\),所以不用担心一条路径走过一条边两次。
那么对于\(DAG\)中的每一条边\((y,x)\),我们有
\[f[x][S\ xor\ prmS[x]]=\sum^{(y,x)}_{y}f[y][S]
\]
答案是\(\sum^{n}_{i=1}f[i][0]\)。
这个算法的时空复杂度均为\(O(n2^m)\),空间会炸。
我们发现,其实所有大于\(\frac{90}{2}\)的质数都是没有用的,因为在\(1\sim n\)中,含有该质因子的数就只有这个质数本身,所以如果一条路径包含这个点,那么这条路径必然会有一个质因子的质数为1。
也就是说,大于\(\frac{90}{2}\)的质数我们都可以根本不用在图中出现,直接删去这个点即可,因为这个点不会造成任何贡献。
那么有用的质因数就只有\(2,3,5,7,11,13,17,19,23,29,31,37,41,43\)这\(14\)个了。
这样时空复杂度就降到了\(O(n2^{14})\),可以过掉本题。
代码
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=100,M=8010,MAXN=(1<<14);
const int prime[15]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43};
int n,m,tot,head[N],prmS[N],in[N];
ll ans,f[N][MAXN];
struct edge
{
int next,to;
}e[M];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void topsort()
{
queue<int> q;
for (int i=1;i<=n;i++)
if (!in[i]) q.push(i);
for (int i=1;i<=n;i++)
f[i][prmS[i]]=1;
while (q.size())
{
int u=q.front();
q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
for (int j=0;j<MAXN;j++)
f[v][j^prmS[v]]+=f[u][j];
in[v]--;
if (!in[v]) q.push(v);
}
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
if (x==47||x==53||x==59||x==61||x==67||x==71||x==73||x==79||x==83||x==89) continue;
if (y==47||y==53||y==59||y==61||y==67||y==71||y==73||y==79||y==83||y==89) continue;
add(x,y); in[y]++;
}
for (int i=2;i<=n;i++)
for (int p=i,j=1;prime[j]<=p && j<=14;j++)
if (!(p%prime[j]))
{
bool cnt=0;
for (;!(p%prime[j]);p/=prime[j]) cnt^=1;
if (cnt) prmS[i]|=(1<<j-1);
}
topsort();
for (int i=1;i<=n;i++)
if (i!=47&&i!=53&&i!=59&&i!=61&&i!=67&&i!=71&&i!=73&&i!=79&&i!=83&&i!=89)
ans+=f[i][0];
printf("%lld",ans);
return 0;
}