Atcoder 236
作者:互联网
AtCoder Beginner Contest 236
\(Ex.Distinct Multiples\)
题意
给定\(N\)个数\(D=(D_1,D_2,...,D_N)\)和一个正整数\(M\),问有多少种方法构造一个长度为\(N\)的序列\(A=(A_1,A_2,...,A_N)\),使得\(A\)满足以下条件:
- \(1\le A_i\le M\)
- \(A_i\neq A_j\) \((i\neq j)\)
- \(D_i|A_i\)
\(2\le N\le 16\) \(1\le M\le 10^{18}\) \(1\le D_i\le M\)
Sol
组合数学
由于\(N\)很小,所以可以考虑用状态压缩。
以这\(N\)个数有几个数相同划分集合,用二进制来表示,那么根据容斥原理,假设当前的集合是\(S\),假设\(S\)在二进制下第\(i_1,i_2,...,i_m\)位是\(1\),那么容斥系数就是\((-1)^{m-1}\times(m-1)!\times \lfloor \frac{M}{LCM(D_{i_1},D_{i_2},...,D_{i_m})}\rfloor\)
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=1<<17;
ll fac[N];
int cal(int s)
{
int cnt=0;
for(int i=0;i<=17;i++)
if((s>>i)&1) cnt++;
return cnt;
}
int lowbit(int s){return s&-s;}
int main()
{
fac[0]=1;
int n;
ll M;
rd(n),rd(M);
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
vector<ll>D(n),xs(n+1);
for(int i=0;i<n;i++) rd(D[i]);
for(int i=1;i<=n;i++) //xs[i]=(i%2?1:-1)*fac[i-1]%mod;
if(i%2) xs[i]=fac[i-1];
else xs[i]=(mod-fac[i-1])%mod;
vector<ll>g(1<<n);
for(int s=0;s<(1<<n);s++)
{
ll v=1;
for(int i=0;i<n;i++)
if((s>>i)&1)
{
ll o=__gcd(v,D[i]);
if(v*1.0*D[i]/o>2E18)
{
v=M+1;
break;
}
v=v*D[i]/o;//求lcm
}
g[s]=(M/v%mod*xs[cal(s)])%mod;
if(g[s]<0) g[s]=(g[s]+mod)%mod;
}
vector<ll>dp(1<<n);
dp[0]=1;
for(int s=1;s<(1<<n);s++)
{
int ns=s&~lowbit(s); //去掉末尾1
for(int t=ns;;t=(t-1)&ns) //每次去掉1个末尾的1
{
dp[s]=(dp[s]+g[t|lowbit(s)]*dp[ns^t])%mod; //ns^t是只有去掉的1的保留的值
if(!t) break;
}
}
cout<<dp.back()<<'\n';
return 0;
}
\(D.Good Vertices\)
题意
有一个有\(N\)个点的有向图,现在总共有\(T\)秒,每\(1\)秒会加入一条有向边。定义一个点\(v\)是漂亮的:从点\(1\)可以到点\(v\)并且经过且只经过\(L\)条边。
现在每一个点\(v\),如果它不是漂亮的点,输出\(-1\),否则输出这个点\(v\)最早被认为是漂亮的点的时间。
\(2\le N\le 100\) \(1\le T\le N^2\) \(1\le N\le 10^9\) 不会出现重边
Sol
把加入的时间当做边权建立一个完全有向图,对于没有加入的边,边权定义为\(inf\)
定义\(dp[i][v]\)表示从点\(1\)到点\(v\)经过且只经过\(i\)条边的最小边权
初始状态:对于点\(2,3,...,N\),\(dp[0][i]=inf\) \(dp[0][1]=0\)
转移就是:\(dp[i][v]=min_{1\le u\le N}max\{dp[i-1][u],w(u,v)\}\)
为什么这里去\(max\)是因为满足条件的边如果后加那么这个时间就应该是后加的时间,比如从点\(1\)到\(u\)走\(i-1\)步,第\(i\)步走\(w(u,v)\),如果前\(i-1\)步中存在大于第\(i\)步的时间,那么这条路径的最早时间应该是这条路径上的最大权值,但我们要求的是最早时间,所以对所有满足条件的路径取\(min\)
继续优化:
将取\(min\)看做加法,将取\(max\)看做乘法
那么\(dp[i][v]=\sum_{u=1}^N(dp[i-1][u]\times w(u,v))\)
写成矩阵的形式
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=105;
int n,T,L;
struct matrix
{
int a[N][N];
matrix(){memset(a,inf,sizeof a);}
matrix operator * (const matrix &t)
{
matrix C;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
C.a[i][j]=min(C.a[i][j],max(a[i][k],t.a[k][j]));
return C;
}
};
int main()
{
rd(n),rd(T),rd(L);
matrix A,B;
for(int i=1;i<=T;i++){
int u,v;
rd(u),rd(v);
B.a[u][v]=i;
}
A.a[1][1]=0;
while(L)
{
if(L&1) A=A*B;
B=B*B;
L>>=1;
}
for(int i=1;i<=n;i++)
if(A.a[1][i]==inf) cout<<-1<<' ';
else cout<<A.a[1][i]<<' ';
return 0;
}
标签:Atcoder,le,...,int,void,236,dp,define 来源: https://www.cnblogs.com/Arashimu0x7f/p/15968697.html