[ARC089D] ColoringBalls
天,感觉全是细节,事实上也如此:
借大佬的细节才过了此题
#include<bits/stdc++.h>
#define maxn 75
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;
int n,K,fac[maxn << 2],invf[maxn << 2],inv[maxn << 2];
int C(int a,int b){ if(a<0 || b<0 || a-b<0) return 0;return fac[a] * 1ll * invf[b] % mod * invf[a-b]%mod; }
char s[maxn];
vi p;int ans;
bool check(){
static bool vis[maxn];
static int loc[maxn];
memset(vis,0,sizeof vis);
int j=1;
for(int i=p.size()-1;i>=0 && p[i];i--){
for(;j <= K && s[j] != 'r';j++);
if(j > K) return 0;
vis[j] = 1 , loc[i] = j , j++;
}
j=1;
for(int i=p.size()-1;i>=0 && p[i];i--){
j = max(j , loc[i]);
for(;j <= K && s[j] != 'b';j++);
if(j > K) return 0;
vis[j] = 1 , loc[i] = j , j++;
}
j=1;
for(int i=0;i<p.size() && p[i]==0;i++){
for(;j <= K && (s[j] != 'r' || vis[j]);j++);
if(j > K) return 0;
vis[j] = 1 , j++;
}
j=1;
for(int i=p.size()-1;i>=0 && p[i] > 1;i--){
j = max(j , loc[i]);
rep(k,2,p[i]){
for(;j <= K && vis[j];j++);
if(j > K) return 0;
vis[j] = 1 , j++;
}
}
return 1;
}
void dfs(int sz,int mx,int lim){
if(sz > n) return;
if(check()){
int sm = fac[p.size()];
for(int i=0,j;i<p.size();i=j){
for(j=i;j<p.size() && p[j] == p[i];j++);
sm = 1ll * sm * invf[j-i] % mod;
}
ans = (ans + 1ll * C(n-sz+mx-1,mx-1) * sm) % mod;
//printf("%d %d %d %d\n",sm,ans,sz,mx);
}
else return;
rep(i,lim,(n-sz+1)/2){
p.pb(i);
dfs(sz+max(2*i-1,1)+(sz!=0),mx+2*i+2,i);
p.pop_back();
}
}
int main(){//freopen("1.in","r",stdin);//freopen("2.out","w",stdout);
scanf("%d%d",&n,&K);
scanf("%s",s+1);
fac[0] = inv[0] = inv[1] = fac[1] = invf[0] = invf[1] = 1;
rep(i,2,(maxn << 2) - 1) fac[i] = 1ll * fac[i-1] * i % mod , inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod,
invf[i] = 1ll * invf[i-1] * inv[i] % mod;
dfs(0,1,0);
printf("%d\n",(ans+mod)%mod);
}
[ARC096C] Everything on It
容斥,枚举有
种元素用了
次,其中有
种元素用了
次。
则答案为:
注意
和
是完全不同的。
注意到有恒等式
证明:考虑组合意义,相当于是拿
个数不选,剩下的
个数分到
组内,如果我们加一个组来装着
个数,那么这个组可能为空,同时发现这个组和别的组不一样,但是第二类斯特林数组之间是没有标号区别的,所以我们就往空组加入一个
(强行规定
所在的组为不选组),则非空和有区别这两个限制都被满足了,方案数就是
。
但是其实这样做很傻,直接类似第二类斯特林数
,
后面那个
中的
就代表了不选这一选项,直接
也没有什么问题。
#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,P,S[maxn][maxn],C[maxn][maxn],pw[maxn];
int upd(int x){ return x += x >> 31 & P; }
int main(){
scanf("%d%d",&n,&P);
rep(i,S[0][0]=C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = upd(C[i-1][j-1] + C[i-1][j] - P) % P;
rep(i,1,n) rep(j,S[i][0]=1,i) S[i][j] = (S[i-1][j] * (j+1ll) + S[i-1][j-1]) % P;
int ans = 0 , ppw = 2;
rep(i,0,n) pw[i] = 1;
per(k,n,0){
int sm = 0;
rep(i,0,k) sm = (sm + 1ll * S[k][i] * pw[i]) % P;
ans = (ans + (k&1?-1ll:1ll)*C[n][k]*ppw%P*sm)%P;
int p2 = 1;
rep(i,0,n) pw[i] = 1ll * p2 * pw[i] % P , p2 = 2ll * p2 % P;
ppw = ppw * 1ll * ppw % P;
}
printf("%d\n",(ans+P)%P);
}
CF1295F Good Contest
题意: 为在 之间均匀随机的离散变量,求 不增的概率。
先离散化,将
离散化后排序得到一个数组
设
表示前
个变量最后一个变量
的概率。
那么
后面那个组合数就是在
中选择
个可以相同的数但是需要无序的方案。(因为最后分配给
的变量是有序的。)
注意这个转移必须要
都包含
这个区间才行。
#include<bits/stdc++.h>
#define maxn 105
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 998244353
using namespace std;
int n,l[maxn],r[maxn],sb[maxn<<1];
int C[maxn][maxn],inv[maxn],f[maxn][maxn],in[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }
int main(){
inv[0] = inv[1] = 1;
int sm = 1;
for(int i=2;i<maxn;i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&l[i],&r[i]),sb[++sb[0]]=l[i],sb[++sb[0]]=(++r[i]) , sm = 1ll * sm * Pow(r[i] - l[i] , mod-2) % mod;
sort(sb+1,sb+1+sb[0]);
sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
for(int i=1;i<sb[0];i++){
int L = sb[i+1] - sb[i] , f = 1;
rep(j,1,n){
f = 1ll * f * (L+j-1) % mod * inv[j] % mod;
C[i][j] = f;
}
}
rep(i,1,sb[0]) f[0][i] = sm;
for(int i=1;i<=n;i++){
l[i] = lower_bound(sb+1,sb+1+sb[0],l[i])-sb , r[i] = lower_bound(sb+1,sb+1+sb[0],r[i])-sb;
rep(j,l[i],r[i]-1){
in[i][j] = 1;
for(int k=i-1;in[k+1][j] && k>=0;k--)
f[i][j] = (f[i][j] + 1ll * f[k][j+1] * C[j][i-k]) % mod;
}
per(j,sb[0]-1,1) f[i][j] = (f[i][j] + f[i][j+1]) % mod;
}
printf("%d\n",(f[n][1]+mod)%mod);
}
Valentines Day Contest 2020 C. Isolation
给出 ,求走 步(每步可以让横坐标或纵坐标 )的方案数使得途中不经过距离原点曼哈顿距离为 的点。
容斥,设 表示走了 步之后到达了 号禁止点(禁止点为距离原点曼哈顿距离为 的点集),至少一共走过了 个禁止点。
所以答案就是
注意到我们直接计算容斥系数可以不需要
这一维。
所以
在我们可以
直接计算两个点之间走
步到达的方案数后,
复杂度就是
的。
设一个点在
另一个点在
,一共走
步要到达。
发现因为有相反的方向(减少的方向)所以计数很困难,考虑如何用上一共走
步的条件。
如果我们把减少横坐标看做增加纵坐标,减少纵坐标看做增加横坐标,
那么横坐标的增加量比纵坐标的增加量大
,总步数又是
,所以我们可以得出增加纵坐标的次数是
,方案数为
但是这样无法区分增加纵坐标和减少横坐标这两种操作(等),
于是我们再反着定义:
把减少横坐标看做增加横坐标,减少纵坐标看做增加横坐标,增加横坐标看做增加纵坐标,增加纵坐标不变。
那么纵坐标的增加量比横坐标的增加量大
。
方案数为
然后发现这样定义,两个组合数的乘积就是我们需要的答案。
如果我们记横坐标之差为
,纵坐标之差为
的话答案就是:
这个方法感觉有点奇怪,特别是拓展到三维的时候他是四个组合数乘起来。
但是他可以
。
#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
using namespace std;
int X,Y,n,D,c[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1)r=1ll*r*b%mod;return r; }
int C(int n,int m){ if(m < 0 || n < 0 || n - m < 0) return 0; return c[n][m]; }
int B(int a,int b,int p){
if(p-a-b&1) return 0;
return C(p,(p-a-b)/2) * 1ll * C(p,(p-a+b)/2) % mod;
}
int x[maxn],y[maxn],cnt;
int f[maxn][maxn];
int main(){
scanf("%d%d%d%d",&X,&Y,&n,&D);
rep(i,c[0][0]=1,maxn-1) rep(j,c[i][0]=1,i) c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;
rep(i,-D,D) rep(j,-D,D) if(abs(i) + abs(j) == D)
x[++cnt] = i , y[cnt] = j;
rep(i,1,n) rep(j,1,cnt){
f[i][j] = -B(abs(x[j]-X),abs(y[j]-Y),i);
rep(k,1,i-1) rep(p,1,cnt)
f[i][j] = (f[i][j] - 1ll * f[k][p] * B(x[j]-x[p],y[j]-y[p],i-k)) % mod;
}
int ans = Pow(4 , n);
rep(i,1,n) rep(j,1,cnt) ans = (ans + f[i][j] * 1ll * Pow(4,n-i)) % mod;
printf("%d\n",(ans+mod)%mod);
}
AGC034F RNG and XOR
有 概率选 来异或你手上的数,问第一次得到 的期望时间。
怎么感觉ZJOI2019开关就是把这道题的FWT拆了拆式子快速实现。
发现多次到达同一个数很难解决,但是发现如果倒过来我们求从
第一次到
的时间并且让
不能转移出来就可以解决多次到达同一个数的问题,因为这样同一个数都变成了
,便于统一处理。
所以设
表示从
第一次到
的时间。
则有
,注意这个式子是对
进行转移,所以
。
写成生成函数就是
注意
不是由上面的方程得到的,而是因为
的和为
,所以卷积之后所有项的和不变,所以解出来的。
那么我们将
变成
则可以发现。
直接写
除法即可,注意到
也就是说我们
后被除的式子中
这项为
(因为
别的都不为
),注意到这东西表达的方程是
,我们考虑就这样让
,发现这个方程可能会无解,因为我们加多了条件,但是没关系,我们解方程是用
解,不会考虑无解。
之后如果得到了
,考虑怎么得到
,假设真实的
。
那么在
之后
,因为
所以每个位置都减去
即可。
#include<bits/stdc++.h>
#define maxn 1<<18|5
#define mod 998244353
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,N;
int a[maxn],b[maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }
void FWT(int *a){
rep(i,0,n-1) rep(j,0,N-1) if(!(j>>i&1)){
int v = j ^ (1 << i) , x = (a[j] + a[v]) % mod , y = (a[j] - a[v]) % mod;
a[j] = x , a[v] = y;
}
}
int main(){
scanf("%d",&n);N = 1 << n;int S=0;
rep(i,0,N-1) scanf("%d",&a[i]),S+=a[i];
S = Pow(S , mod-2);
rep(i,0,N-1) a[i] = 1ll * a[i] * S % mod;
a[0]-- , b[0] = 1 << n;
rep(i,0,N-1) b[i]--;
FWT(a),FWT(b);
rep(i,0,N-1) a[i] = 1ll * Pow(a[i],mod-2) * b[i] % mod;
FWT(a);int ivN = Pow(N , mod-2);
rep(i,0,N-1) a[i] = 1ll * a[i] * ivN % mod;
per(i,N-1,0) a[i] = (a[i] - a[0]) % mod;
rep(i,0,N-1) printf("%d\n",(a[i]+mod)%mod);
}
2019-2020 XX Opencup GP of Tokyo E . Count Modulo 2
给出 ,求 的方案数 ,其中 是 中的一个,
活生生猜出来的解法
因为要
,所以假设说一个方案中
各有
个,那么和他只是顺序不同的方案数有
种。
由库默尔定理我们可以知道一个二进制位
只能由一个
所拥有,这样就可以解决
的问题,把
的每个二进制位拿出来即可。(有个更牛逼的结论,在
意义下
)
但是
。
考虑
,所以当我们在考虑第
位的时候,剩余的
后面都无法让
变为
。
所以我们从大到小枚举
,用
保存
位,然后转移,
的时候需要枚举每个
位来转移,时间复杂度
#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
using namespace std;
LL n,S;
int a[maxn],K;
bitset<maxn>f[2],t;
int main(){
int T;
for(scanf("%d",&T);T--;){
scanf("%lld%lld%d",&n,&S,&K);
rep(i,1,K) scanf("%d",&a[i]);
int now = 1 , pre = 0;
f[now].reset(),f[pre].reset();
f[now][0] = 1;
per(i,60,0){
swap(now,pre);
t.reset(),f[now].reset();
if(n >> i & 1){
rep(j,1,K) t ^= f[pre] >> a[j];
}
else t = f[pre];
if(i){
rep(j,0,(maxn-4)/2) if(t[j])
f[now].flip(j << 1 | (S >> (i-1) & 1));
}
else
f[now] = t;
}
cout << f[now][0] << endl;
}
}
2019-2020 XX Opencup GP of Tokyo I. Amidakuji
求 个 的排列的映射 ,对于所有 都存在 ,其中 或
设
则我们让
这样可以组合出来
以内所有奇数,包括负的。
证明可以考虑归纳证明。
如果
是奇数,那么正负两边已经能让我们到达所有位置。
如果
是四的倍数,可以构造排列
来让我们可以使得一个位置移动奇数或偶数,注意这时这个排列是可以让
的奇偶性不同的,所以
这个排列就不需要了(刚好卡进),我们只需要找距离最近也就是
的那边走。
如果
是
,可以构造排列
和
即可。
#include<bits/stdc++.h>
#define maxn 1005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define pb push_back
using namespace std;
int n;
vector<vector<int> >p;
int main(){
scanf("%d",&n);
if(n == 2){
puts("-1");
return 0;
}
int L = 1 , l = 0;
for(;L <= n; L<<=1,l++);
l--;
rep(i,0,l-(n % 2 == 0)){
vector<int>r;
rep(j,0,n-1) r.pb((j+(1<<i)) % n);
p.pb(r);
}
if(n % 4 == 0){
vector<int>r;
rep(j,0,n/4-1)
r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
p.pb(r);
}
else if(n % 2 == 0){
vector<int>r;
rep(j,0,n/4-1)
r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
r.pb(n-2),r.pb(n-1);
p.pb(r);
r.clear();
rep(j,0,n-5) r.pb(j);
r.pb(n-4+2),r.pb(n-4+3),r.pb(n-4+1),r.pb(n-4);
p.pb(r);
}
printf("%d\n",p.size());
rep(i,0,p.size()-1)
rep(j,0,n-1)
printf("%d%c",p[i][j]+1," \n"[j==n-1]);
}
CodeForces 1292F Nora’s Toy Boxes
题意:给出 个不同的 的数,当 满足 都未被删去, 并且 时可以将 删去,求能删除最多数的删除序列数。
将
视作
的连边,则我们对于每个弱连通图分别计算方案。
(注意下文的讨论中图是弱联通的。)
显然这个如果
有边,则
有边。
所以如果在某次删除中需要找到一个
,这个
一定可以没有入度。
我们把没有入度的点集看做
,其他点看做
。
则我们需要求最少删到还有多少点,换个方向考虑,假如一开始我们让一些点存在,然后让这些
,
存在,
不存在的拓展出
存在,如果能拓展出所有点,那么这个拓展方案反过来就和合法的删点方案一一对应。
首先
中的点不可能被删,所以
中的点一开始都是存在的,删最多的点意味着
中一开始存在的点要尽量少,最少为
,接下来我们给出构造的方案使得
中一开始的点数为
。
对于
中存在的点,找能够到达他的所有的
中的点
,那么
能到达的点都会变为存在,再重复这个过程即可,容易发现弱连通图中的所有点都会变为存在,也就是
中任意一个点开始我们都可以让所有点存在。
考虑如何根据这个过程构造出删点方案,我们定义一种新的标记,这种标记只会打在
的点中,对于
中一开始的点,我们找能够到达他的所有的
中的点
,给
打上标记,之后我们找下一个被删除的点
,这个
只需要保证能够到达他的所有的
中的点存在一个
,
是有标记的即可,接下来给能够到达他的所有的
中的点
打上标记,如此重复即可得到一个删点方案。
可以证明
中的点数
,首先可以认为
中的点都应该
,否则没有出边也没有入边不满足强联通,对于
的所有点,
中的点构成了一个反链,其大小
的最小链覆盖,我们可以给出一个链覆盖为
等形如一个奇数
的
条链,所以
中的点数
。
于是我们用一个状压
,
表示目前集合
的点打上了标记,已经删去了
个点,
注意到我们不应该把
中的点是否被选过纳入状态,所以我们需要有两种转移。
一种是删去能到达他的只有
中的点,那么第一维不变,第二维
,需要预处理出能到达他的只有
中的点数
,通过
统计出还没被删的点数来做决策。
第二种是删去能使
变大的点并且这个点能被
到达,这个点显然不会被删过,所以我们可以有一个
的
。
#include<bits/stdc++.h>
#define maxn 65
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;
int n,a[maxn];
int F[maxn],in[maxn],C[maxn][maxn],sta[1<<15],f[1<<15],g[1<<15][maxn];
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }
vi G[maxn];
int main(){
scanf("%d",&n);
rep(i,1,n) scanf("%d",&a[i]);
sort(a+1,a+1+n);
rep(i,1,n) rep(j,i+1,n) if(a[j] % a[i] == 0){
int x = Find(j) , y = Find(i);
in[j]++;
if(x ^ y) F[x] = y;
}
rep(i,C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
rep(i,1,n) G[Find(i)].pb(i);
int ans = 1 , hd = 0;
rep(i,1,n) if(G[i].size() > 1){
vi S;
for(int v:G[i]) if(!in[v]) S.pb(v);
int N = 1 << S.size();
memset(f,0,sizeof f) , memset(g,0,sizeof g);
int cnt = 0;
for(int v:G[i]) if(in[v]){
cnt ++;
rep(j,0,S.size()-1)
if(a[v] % a[S[j]] == 0)
sta[v] |= 1 << j;
f[sta[v]] ++;
}
rep(i,0,S.size()-1) rep(j,0,N-1) if(j >> i & 1)
f[j] += f[j-(1<<i)];
g[0][0] = 1;
rep(j,0,N-1) rep(k,0,cnt) if(g[j][k]){
if(k < f[j]) g[j][k+1] = (g[j][k+1] + 1ll * g[j][k] * (f[j] - k)) % mod;
for(int p:G[i]) if(in[p] && ((sta[p] & j) != sta[p]) && ((sta[p] & j) || j == 0))
g[j | sta[p]][k+1] = (g[j|sta[p]][k+1] + g[j][k]) % mod;
}
ans = 1ll * ans * g[N-1][cnt] % mod * C[hd + cnt - 1][cnt - 1] % mod;
hd += cnt - 1;
}
printf("%d\n",ans);
}