平邑一中集训被容斥 dp 和数位 dp 吊起来打
打算回来补补 dp
结果是个神仙数学题
看到题一开始以为是个仪仗队
后来才发现 (i) 和 (j) 限制不同,欧拉函数不能一下切掉
看了题解之后才知道是容斥题
求
可以考虑设 (g[x]) 为能够被x整除的二元组 ((i,j)) 的个数
那么显然,$$g[x]=leftlfloorfrac{n}{x} ight floor imesleftlfloorfrac{m}{x} ight floor$$
设 (f[x]) 为最大公因数为 (x) 的二元组个数,这玩意不好求
考虑容斥
然后f就变得可求了
我似乎在 day10 的 T1 里面想到过类似的东西
复杂度大概是调和级数(O(nlog nlog n))
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF 1ll<<30
#define ill unsigned long long
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 1e5 + 5;
int f[np];
signed main()
{
int n,m;
read(n);
read(m);
for(int x = min(n , m);x;x--)
{
f[x] += (n/x) * (m/x);
for(int i=2;x * i <= min(n , m);i++)
f[x] -= f[i * x];
}
int Ans= 0 ;
for(int i=1;i<=min(n,m);i++)
{
Ans += f[i] * i;
}
Ans*=2;
Ans -=m * n;
cout<<Ans;
}
这是个比整除分块更优的仪仗队解法
如果只有一个乌龟,则是经典的格路计数问题。
考虑一个合法的方案可能是$$(1,2)->(n-1,m),(2,1)->(n,m-1)$$
不合法的方案必定是$$(1,2)->(n,m-1),(2,1)->(n-1,m)$$
那么我们考虑一个可能合法方案的不合法情况
该情况两条路径必定有交点,考虑对最后一个交点的两端路径进行翻转,那么$$(1,2)->(n-1,m),(2,1)->(n,m-1)$$可以翻转为$$(1,2)->(n,m-1),(2,1)->(n-1,m)$$
而且翻转后与翻转前的方案一一对应
所以答案是$$solve(1,2,n-1,m) imes solve(2,1,n,m-1) - solve(1,2,n,m-1) imes solve(2,1,n-1,m)$$
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
template<typename _T>
inline void read(_T &x)
{
x = 0;int f= 1;char s = getchar();
while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 3e3+ 5;
char s[np][np];
int a[np][np];
int f[np][np];
int n,m;
inline int solve(int st_x,int st_y,int end_x,int end_y)
{
f[st_x][st_y] = 1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j])
{
f[i][j] = f[i-1][j] + f[i][j-1] + f[i][j];
f[i][j]%=mod;
}
else f[i][j] = 0;
}
}
return f[end_x][end_y];
}
signed main()
{
read(n);
read(m);
for(int i=1;i<=n;i++)
{
scanf("%s",s[i] + 1);
int len = strlen(s[i] + 1);
for(int j=1;j<=len;j++)
a[i][j] = s[i][j]=='.'?1:0;
}
int x = solve(1,2,n-1,m);
memset(f,0,sizeof(f));
int y = solve(2,1,n,m-1);
memset(f,0,sizeof(f));
int xx = solve(1,2,n,m-1);
memset(f,0,sizeof(f));
int yy = solve(2,1,n-1,m);
cout<< ((x * y - xx * yy + mod)%mod + mod) %mod;
}
有思维难度的容斥 dp
我们先展示两种科技:子集反演、二项式反演
子集反演:
二项式反演(基本:
扩展:
二项式反演有广义容斥证明方法,这里不写了
子集反演本质就是广义容斥,很好想,不证了
P3349 [ZJOI2016]小星星
看到 N 的范围很小,直接dp
看一个朴素的状压 dp 解法
dp[i][j][S] 表示以 i 为根的子树选 j 标号,它的子树选了 S 这个集合的标号
每次转移枚举子集即可,总复杂度 (O(n^33^n))
显然过不去。
我们考虑暴力中求的是唯一对应,即 i 的子树唯一对应集合 S
优化掉枚举子集的复杂度,需要使用容斥原理,
将 dp 方程改为以 i 为根的子树选 j 标号,它的子树至多在 S 这个集合内选标号。
每次枚举一个 S,然后在树上 dp 即可
统计答案的时候用一下前面提到的『子集反演』科技,逆向求 g
虽然提倡不要学科技,但是题解中不用科技硬找容斥系数也太奇怪了……
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x&(-x))
const int mod = 1e9 + 7;
template<typename _T>
inline void read(_T &x)
{
x = 0;int f= 1;char s = getchar();
while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 19;
const int npp = (1ll << 21) + 5;
int n,m;
int head[np] , ver[np * 4] , nxt[np * 4];
int tit;
inline void add(int x,int y)
{
ver[++tit] = y;
nxt[tit] = head[x];
head[x] = tit;
}
int Edge[np][np];
int f[np][np];
int g[npp];
inline void dfs(int x , int fa,int S)
{
for(int i=1;i<=n;i++) f[x][i] = 1ll;
for(int i=head[x] , v;i;i=nxt[i])
{
v = ver[i];
if(v == fa) continue;
dfs(v , x,S);
for(int i=1;i<=n;i++)
{
if(!(1ll<<(i-1) & S)) continue;
int op = 0;
for(int j=1;j<=n;j++)
{
if(i == j) continue;
if(!(1ll<<(j-1) & S)) continue;
if(Edge[i][j]) op += f[v][j];
}
f[x][i] *= op ;
}
}
}
signed main()
{
read(n);
read(m);
for(int i=1,u,v;i<=m;i++)
{
read(u);
read(v);
Edge[u][v] = 1;
Edge[v][u] = 1;
}
for(int i=1,u,v;i<=n-1;i++)
{
read(u);
read(v);
add(u ,v);
add(v ,u);
}
int f_ = 0;
for(int i = 0; i < 1ll<<n ; i++)
{
dfs(1 , 0 , i);
for(int j=1;j<=n;j++)
g[i] += f[1][j];
int cnt_x = 0 ;
for(int x = i;x;x-=lowbit(x)) cnt_x++;
f_ += (n - cnt_x) & 1?-g[i]:g[i];
}
cout<<f_;
}
# P2167 [SDOI2009]Bill的挑战
有二项式反演做法……
但我不会,直接暴力状压 dp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x & (-x))
const int mod = 1000003;
template<typename _T>
inline void read(_T &x)
{
x = 0;int f= 1;char s = getchar();
while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 17;
const int npp = (1 << 17) + 5;
int f[np * 4][npp];
char c[np][np << 2];
int v[np * 4][29];
inline int Gets(char x)
{
return x - 'a' + 1;
}
int st[2333];
int top;
inline void print(int x)
{
while(x) st[++top] = x&1 , x>>=1;
for(int i=1;i<=top;i++)
{
std::cout<<st[i];
}
std::cout<<'
';
}
signed main()
{
int T;
read(T);
while(T--)
{
memset(f,0,sizeof(f));
int n,k;
read(n);
read(k);
int len;
for(int i=1;i<=n;i++)
{
scanf("%s",c[i] + 1);
len = strlen(c[i] + 1);
}
for(int i=1;i<=len;i++)
{
for(int j=1;j<=26;j++)
{
int op = 0;
for(int c_=1;c_<=n;c_++)
{
if(Gets(c[c_][i]) == j || c[c_][i] == '?')
{
op += 1 << c_ - 1;
}
}
v[i][j] = op;
}
}
f[0][(1<<n) - 1] = 1;
for(int i=1;i <= len;i++)
{
for(int ch=1;ch<=26;ch++)
{
for(int T = 0 ; T < 1<<n;T++)
{
f[i][T & v[i][ch]] += f[i - 1][T];
f[i][T & v[i][ch]] %= mod;
}
}
}
int Ans =0 ;
for(int i=0;i < 1<<n ; i++)
{
int cnt_ = 0;
for(int u = i;u ; u -= lowbit(u)) cnt_++;
if(cnt_ == k) Ans += f[len][i] , Ans %= mod;
}
cout<<Ans<<'
';
}
}