2020ICPC江西省大学生程序设计竞赛

比赛链接

2020ICPC 江西省大学生程序设计竞赛

A.Simple Math Problem

求解 \(\sum_{i=1}^{n} \sum_{j=1}^{i}[\operatorname{gcd}(i, j)==1] f(j)\),其中 \(f(j)\)\(j\) 的数位和

解题思路

暴力,容斥原理

先预处理所有的 \(f(j)\),对于每个 \(f(j)\),找出 \(j\leq i\leq n\)\(i\)\(j\) 互质的个数 \(cnt\)\(cnt\times f(j)\) 即为 \(j\) 的贡献,关键在于计算 \(cnt\),根据前缀和,即找 \(1\leq i\leq n\)\(i\)\(j\) 互质的个数,由于 \(n\geq j\),直接计算与 \(j\) 互质的个数不好做,相反计算与 \(j\) 不互质的个数比较方便,即先对 \(j\) 分解质因数种类:\(p_1,p_2,\dots,p_k\),然后找 \(1\sim n\) 内倍数为 \(p_1,\dots,p_k\) 的数个数,其中也得用到容斥原理

设平均质因子种数为 \(C\),则:

  • 时间复杂度:\((n\times C\times 2^C)\)

莫比乌斯函数

\(\sum_{i=1}^{n} \sum_{j=1}^{i} f(j)[(i, j)=1]\) 等价于 \(\sum_{i=1}^{n} \sum_{j=i}^{n} f(i)[(i, j)=1]\),由容斥原理,\(\sum_{i=1}^{n} \sum_{j=i}^{n} f(i)[(i, j)=1]\Leftrightarrow\)\(\sum_{i=1}^{n} \sum_{j=1}^{n} f(i)[(i, j)=1]-\sum_{i=1}^{n} \sum_{j=1}^{i} f(i)[(i, j)=1]+f(1)\),而 \([(i, j)=1]=\sum_{d\mid (i,j)}\mu(d)\),则 \(\Leftrightarrow \sum_{i=1}^{n} \sum_{j=1}^{n} f(i)\sum_{d\mid (i,j)}\mu(d)-\sum_{i=1}^{n} f(i) \phi(i)+f(1)\),将 \(d\) 提到外面,\(d\) 的范围为 \([1,n]\),此时需满足 \(d\mid (i,j)\),设 \(i'=i/d,j'=j/d\),变换,\(\Leftrightarrow \sum_{d=1}^n\mu(d)\frac{n}{d} \sum_{i'=1}^{\frac{n}{d}}f(i'd)-\sum_{i=1}^{n} f(i) \phi(i)+f(1)\)

求解后面一部分,直接线性筛求解欧拉函数即可,前面一部分计算 $ \sum_{i'=1}^{\frac{n}{d}}f(i'd)$ 需 \(O(调和级数)\) 复杂度求解,故:

  • 时间复杂度:\(O(nlogn)\)

代码

  • 莫比乌斯函数
// Problem: A Simple Math Problem
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/A
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e5+5;
int n,prime[N],m,v[N],phi[N],u[N],f[N],g[N];
void init(int n)
{
	u[1]=phi[1]=f[1]=1;
	for(int i=2;i<=n;i++)
	{
		f[i]=f[i/10]+i%10;
		if(v[i]==0)
		{
			prime[++m]=v[i]=i;
			u[i]=-1;
			phi[i]=i-1;
		}
		for(int j=1;j<=m&&i*prime[j]<=n;j++)
		{
			if(v[i]<prime[j])break;
			if(i%prime[j]==0)u[i*prime[j]]=0;
			else
				u[i*prime[j]]=-u[i];
			v[i*prime[j]]=prime[j];
			phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]);
		}
	}
    for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j+=i)g[i]+=f[j];
}
int main()
{ 
    cin>>n;
    init(n);
    LL res=0;
    for(int i=1;i<=n;i++)
    	res+=1ll*u[i]*(n/i)*g[i]-f[i]*phi[i];
    res+=f[1];
    cout<<res;
    return 0;
}
  • 暴力
// Problem: A Simple Math Problem
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/A
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e5+5;
int f(int x)
{
	int res=0;
	while(x)res+=x%10,x/=10;
	return res;
}
int F[N],n,prime[N],m,v[N];
void init(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(v[i]==0)
			v[i]=prime[++m]=i;
		for(int j=1;j<=m&&i*prime[j]<=n;j++)
		{
			if(v[i]<prime[j])break;
			v[i*prime[j]]=prime[j];
		}
	}
	for(int i=1;i<=n;i++)F[i]=f(i);
}
vector<int> Div(int x)
{
	vector<int> res;
	unordered_set<int> S;
	while(v[x])
	{
		if(!S.count(v[x]))S.insert(v[x]),res.pb(v[x]);
		x/=v[x];
	}
	return res;
}
int cal(int x,int n)
{
	vector<int> a=Div(x);
	int res=0;
	if(x>n)
	{
		res=x;
		for(int i:a)res/=i,res*=(i-1);
		if(x==1)res--;
		return res;
	}
	res=n;
	int sz=a.size();
	for(int i=0;i<(1<<sz);i++)
	{
		int t=1,sign=1;
		for(int j=0;j<sz;j++)
			if(i>>j&1)t*=a[j],sign*=-1;
		if(t!=1)//注意
			res+=sign*n/t;
	}
	return res;
}
int main()
{
    init(N-1);
    cin>>n;
    LL res=0;
    for(int i=1;i<=n;i++)res+=1ll*F[i]*(cal(i,n)-cal(i,i-1));
    cout<<res;
    return 0;
}

C.Charging

数轴上有 \([1, n]\) 一共 \(n\) 个点, \(m\) 个区间分别是 \(\left[l_{i}, r_{i}\right]\) ,设 \(tot\) 为所选取的区间数量, \(x\) 为所有所选取的区间的交集长度, 求 \(\min (\) tot,\(x)\) 的最大值

解题思路

树状数组,二分

所选取的交集区间一定是某个区间的左端点,某个区间的右端点,不妨枚举所选取区间的右端点 \(r\),即按将所有区间按右端点从大到小排序,枚举右端点时前面的点的右端点都满足右端点条件,设所选取区间的左端点为 \(l\),则利用树状数组可计算前面的区间左端点不大于 \(l\) 的点即满足包含区间 \([l,r]\) 的区间的个数,即选取的区间的个数 \(f=(l)=tot\),此时交集长度为 \(r-l+1\),而要求 \(min(r-l+1,f(l))\) 最大,其中 \(r\) 固定,\(r-l+1\) 为减函数,\(f(l)\) 为增函数,当两者最接近时最优,故可二分 \(l\),使其满足 \(r-l+1\leq f(l)\) 的最小的 \(l\),答案在 \(l\)\(l-1\)

  • 时间复杂度:\(O(nlognlogn)\)

代码

// Problem: Charging
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/C
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
// #define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=3e5+5;
int n,m,res,tr[N];
struct A
{
	int l,r;
	bool operator<(const A &o)
	{
        if(r==o.r)return l<o.l;
		return r>o.r;
	}
}a[N];
int ask(int x)
{
	int res=0;
	for(;x;x-=x&-x)res+=tr[x];
	return res;
}
void add(int x,int y)
{
	for(;x<N;x+=x&-x)tr[x]+=y;
}
int main()
{
    read(n),read(m);
    for(int i=1;i<=m;i++)read(a[i].l),read(a[i].r);
    sort(a+1,a+1+m);
    for(int i=1;i<=m;i++)
    {
    	int l=1,r=a[i].r;
    	add(a[i].l,1);
    	while(l<r)
    	{
    		int mid=l+r>>1;
    		if(a[i].r-mid+1<=ask(mid))r=mid;
    		else
    			l=mid+1;
    	}
        res=max(res,min(ask(l),a[i].r-l+1));
		l--;
        res=max(res,min(ask(l),a[i].r-l+1));
    }
    cout<<res;
    return 0;
}

D.Chinese Valentine's Day

给出 \(n\) 个整数字符串,要求所有本质不同的子串的和

解题思路

后缀自动机

本质上是广义后缀自动机,但可转化为普通后缀自动机,即在 \(n\) 个字符串之间插入一个分割字符,将 \(n\) 个字符串合并为一个字符串,构建 \(SAM\),由于 \(SAM\) 的有向无环图中的所有路径与所有子串一一对应,设某一个状态节点 \(u\) 的贡献为 \(res[u]\),每次按某一个字符 \(i\) 扩展到状态节点 \(v\),贡献转移即 \(res[v]+=10\times res[u]+i\times sz[u]\),其中 \(sz[i]\) 表示状态节点 \(i\) 表示的子串数量即 \(|endpos[i]|\),每次扩展需要前一个状态转移过来,拓扑排序计算贡献即可,同时计算贡献时只需在非分割字符扩展时计算

  • 时间复杂度:\(O(\sum_{i=1}^{n}|s[i]|)\)

代码

// Problem: Chinese Valentine's Day
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int mod=1e9+7,N=3e6+5;
int n,lst=1,cnt=1,sz[N],res[N],in[N];
char s[N];
struct Node
{
	int len,fa;
	int ch[11];
}node[N];
void extend(int c)
{
	int p=lst,np=lst=++cnt;
	node[np].len=node[p].len+1;
	for(;p&&!node[p].ch[c];p=node[p].fa)node[p].ch[c]=np;
	if(!p)node[np].fa=1;
	else
	{
		int q=node[p].ch[c];
		if(node[q].len==node[p].len+1)node[np].fa=q;
		else
		{
			int nq=++cnt;
			node[nq]=node[q];
			node[nq].len=node[p].len+1;
			node[q].fa=node[np].fa=nq;
			for(;p&&node[p].ch[c]==q;p=node[p].fa)node[p].ch[c]=nq;
		}
	}
}
void topSort()
{
	queue<int> q;
	for(int i=1;i<=cnt;i++)
	{
		for(int j=0;j<=10;j++)
		{
			int to=node[i].ch[j];
			if(to)in[to]++;
		}
	}
	for(int i=1;i<=cnt;i++)
		if(!in[i])
		{
			sz[i]=1;
			q.push(i);
		}
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<=10;i++)
		{
			int y=node[x].ch[i];
			if(!y)continue;
			if(i!=10)
			{
				res[y]=(res[y]+1ll*res[x]*10+i*sz[x])%mod;
				sz[y]=(sz[y]+sz[x])%mod;	
			}
			if(--in[y]==0)q.push(y);
		}
	}
}
int main()
{
    scanf("%d",&n);
    while(n--)
    {
    	scanf("%s",s);
    	for(int i=0;s[i];i++)extend(s[i]-'0');                                                                        ;
    	extend(10);                                                        
    }
    topSort();
    LL ret=0;
    for(int i=2;i<=cnt;i++)
		ret=(ret+res[i])%mod;
    cout<<ret;
    return 0;
}

E.Color Sequence

给定一个颜色序列,求它有多少个颜色出现次数都是偶数的连续子序列

解题思路

思维

颜色数量比较少,可以考虑用二进制来表示每一种颜色,要求每种颜色出现次数为偶数,即表示的连续子序列异或值为 \(0\),不妨先求出前缀异或值 \(s\),考虑每个数的贡献,对于当前数下标 \(i\),求前面有多少个数 \(j\),满足 \(s[i]\oplus s[j-1]=0\),直接统计前面 \(s[i]\) 的个数即可

  • 时间复杂度:\(O(n)\)

代码

// Problem: Color Sequence
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/E
// Memory Limit: 131072 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
// #define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e6+5;
int n,s[N],cnt[1<<21];
LL res;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	int x;
    	cin>>x;
    	s[i]=s[i-1]^(1<<x);
    }
    cnt[0]=1;
    for(int i=1;i<=n;i++)res+=cnt[s[i]],cnt[s[i]]++;
    cout<<res;
    return 0;
}

F.Magical Number

定义魔数:其前缀表示的数都能整除该数的位数,每一个个位数都由下列火柴棒组成:
image
\(n\)\(1≤n≤10^{100}\)) 个火柴棒恰能组成的最大的数

解题思路

暴力dfs

对于 \(i\) 位的魔数 \(x\),后面组成的数 \(y=x+[0,9]\),共有 \(10\) 条分支,要求 \(y\) 整除 \(i+1\),这样的条件可想而知,分支数会越来越少,不妨考虑暴力 \(dfs\),用 \(int\) 速度控制在了 \(1\) 秒内,不妨直接用 \(\_\_int128\),会发现最大值为 \(3608528850368400786036725\),没有超出 \(\_\_int128\) 的范围,所以位数大概到了 \(24\) 的量级就没有出现分支了

共有 \(24\) 层,每层直接最多有 \(10\) 个分支,但实际上很少,故:

  • 时间复杂度:\(O(常数)\)

代码

// Problem: Magical Number
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/F
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

template <typename T>
void print(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x < 10) putchar(x + 48);
    else print(x / 10), putchar(x % 10 + 48);
}

#define int __int128

string s;
int n,res=-1,a[]={6,2,5,5,4,5,6,3,7,6};
void dfs(int len,int x,int cost)
{
	if(cost<=0)
	{
		if(cost==0)res=max(res,x);
		return ;
	}
	for(int i=0;i<=9;i++)
	{
		int y=x*10+i;
		if(y%(len+1)==0)dfs(len+1,y,cost-a[i]);
	}
}
signed main()
{
    cin>>s;
    if(s.size()<=3)
    {
    	n=atoi(s.c_str());
    	for(int i=1;i<=9;i++)
    		dfs(1,i,n-a[i]);
    }
    print(res);
    return 0;
}

G.Mathematical Practice

\(n\) 个数,要求 \(m\) 个子集,使得子集之间没有交集,求方案数

解题思路

放球问题

由于 \(n,m\) 比较大,考虑结论
问题转化:将 \(n\) 个不同的球放入 \(m\) 个不同盒子(不一定非要放入盒子中)的方案数,即经典放球问题,每个球有 \(m+1\) 种选择,答案即为 \((m+1)^n\)

  • 时间复杂度:\(O(logn)\)

代码

// Problem: Mathematical Practice
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/G
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int mod=998244353;
int n,m;
int ksm(int a,int b,int p)
{
	int res=1%p;
	while(b)
	{
		if(b&1)res=1ll*res*a%p;
		a=1ll*a*a%p;
		b>>=1;
	}
	return res;
}
int main()
{
    cin>>n>>m;
    cout<<ksm(m+1,n,mod);
    return 0;
}

J.Split Game

一张 \(n \times m\) 的纸,两个人轮流操作,可以沿着一条直线切,当有人切出 \(1 \times 1\) 的纸时输掉比赛,问最后谁赢

解题思路

博弈论,sg函数

219. 剪纸游戏 不同的是,这里要求切出 \(1 \times 1\) 的纸时输掉比赛,则 \(1\times 1\) 为终止态,在进行剪纸时不能剪到 \(1\times 1\) 的状态再计算 mex 即可

  • 时间复杂度:\(O(n^3)\)

代码

// Problem: Split Game
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/J
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=155;
int f[N][N],n,m;
int sg(int n,int m)
{
	if(f[n][m]!=-1)return f[n][m];
	unordered_set<int> s;
	for(int i=1;i<=n-i;i++)
		if(m!=1||i!=1&&n-i!=1)s.insert(sg(i,m)^sg(n-i,m));
	for(int i=1;i<=m-i;i++)
		if(n!=1||i!=1&&m-i!=1)s.insert(sg(n,i)^sg(n,m-i));
	for(int i=0;;i++)
		if(!s.count(i))return f[n][m]=f[m][n]=i;
}
int main()
{
	memset(f,-1,sizeof f);
	f[1][1]=0;
	while(cin>>n>>m)puts(sg(n,m)?"Alice":"Bob");    
    return 0;
}

L.WZB's Harem

给你一张 \(n \times n(n \leq 20)\) 的图,图上有 \(0\)\(1\) 两种数字,现在有 \(n\) 个互不相同的皇后,需要将这 \(n\) 个皇后放在 \(0\) 的位置上,并且保证这 \(n\) 个皇后之间不存在任意两个皇后处于同一行、同一列,问方案数。

解题思路

状压dp

  • 状态表示:\(f[i][j]\) 表示前 \(i\) 行,状态为 \(j\) 的方案数,\(j\) 表示前 \(i\) 个皇后选在哪些列的状态
  • 状态计算:\(f[i][j|1<<k]+=f[i-1][j]\)
    分析:\(j\) 二进制下含有 \(i-1\)\(1\),且第 \(k\) 位为 \(0\),则在 \(a[i][j]=0\) 的前提下可转移到第 \(i\)

\(O(n^2)\) 枚举行和列,再枚举含有对应 \(1\) 的状态,最坏情况下有 \(C_n^{\frac{n}{2}}\) 种状态,则:

  • 时间复杂度:\(O(n^2\times C_n^{\frac{n}{2}})\)

代码

// Problem: WZB's Harem
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36726/L
// Memory Limit: 262144 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=20,mod=1e9+7;
int f[N][1<<N],n,a[N][N];
vector<int> b[N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    	for(int j=0;j<n;j++)cin>>a[i][j];
	for(int i=0;i<(1<<n);i++)b[__builtin_popcount(i)].pb(i);
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
		{
			if(a[i][j])continue;
			for(int k=0;k<b[i].size();k++)
			{
				if((b[i][k]>>j&1)==0)
				{
					if(i)
						f[i][b[i][k]|1<<j]=(f[i][b[i][k]|1<<j]+f[i-1][b[i][k]])%mod;
					else
						f[i][b[i][k]|1<<j]++;
				}
			}
		}
	int res=f[n-1][(1<<n)-1];
	for(int i=1;i<=n;i++)res=1ll*res*i%mod;
	cout<<res;
    return 0;
}
posted @ 2022-06-27 22:02  zyy2001  阅读(210)  评论(1编辑  收藏  举报