杂题选做 Ⅰ
上升序列
有两个长度为 \(n\) 的单调不降序列 \(a,b\),你可以对 \(a\) 进行不超过 \(m\) 次操作:
• 选择一个下标 \(i\) 和一个整数 \(x\),把 \(a_i\) 变成 \(a_i+x\)。这里 𝑥 可以是负数。
操作一次的代价为 \(x^2\)。并且你需要保证每次操作完 \(a_i\) 还是单调不降的。
你要把 \(a\) 变成 \(b\),问最小的代价之和。答案对 \(998244353\) 取模。
如果无解,输出-1。\(n,m \leq 10^5, a_i,b_i \leq 10^9\),保证 \(a,b\) 单调不降。
每次操作完要求单调不降的限制相当于没有,因为如果 \(a_i\) 当前操作不合法,总可以先操作不合法的那一侧,再操作 \(a_i\)。由于序列单调不降,这样递归下去操作不会成环。
令 \(f(i,j)\) 表示把 \(i\) 分成 \(j\) 个数 \(x_1,\dots,x_j\) 时 \(\sum x_j^2\) 的最小值。不难发现 \(f(i,j)\) 关于 \(j\) 是下凸的,所以用优先队列维护一个最大的 \(\Delta = f(i,j)-f(i,j+1)\) 就行了。
复杂度 \(O((n+m) \log n)\)。
竞赛图
给你一个 \(n\) 个点的竞赛图,求有多少个点集 \(S\) 满足 \(S\) 的诱导子图是强连通的。
其中竞赛图是一个有向图,满足任意两个不同的点之间均存在且仅存在一条有向边相连。\(T \leq 10\) 组测试数据,\(n \leq 24\)
考虑将每个 scc 缩点后形成的 DAG,由于图是竞赛图,所以拓扑序是唯一的。
那么直接做就是 \(O(T \cdot n2^n)\) 的,无法通过。
考虑到任意两点之间有且仅有一条有向边相连,所以若一个强连通的点集 \(S\) 中的所有点都能走到另一个点 \(k\),则 \(S+k\) 一定不合法。反过来,每个不合法的点集 \(S\) 也一定存在合法的一个子集 \(T\),满足 \(T\) 与 \(S-T\) 间的边都是从 \(T\) 指向 \(S-T\) 的。
故 \(O(2^n)\) 预处理出每个点集 \(S\) 中每个点出边集合的交 \(R\),若 \(S\) 合法,则 \(\forall T \in R, S|T\) 一定不合法。
据此不难 \(O(2^n)\) 递推计算每个点集的合法性。
复杂度 \(O(T \cdot 2^n)\)。
宝宝拼字母
定义两个字符串 \(A,B\) 是相似的当且仅当可以把 \(A\) 中某个长度不超过 \(K\) 的子串(可以为空串)替换为另一个长度不超过 \(K\) 的串得到 \(B\)
给定 \(N\) 个仅包含英文大写字母的串,求出有多少对字符串是相似的。\(N,K,\sum |S| \leq 5\times 10^5\)。
考虑两个字符串 \(A,B\),不妨设 \(|A| \leq |B|\),则它们相似的充要条件是 \(\operatorname{LCP}(A,B)+\operatorname{LCS}(A,B)+K \geq |B|\)。
假设我们已经枚举了一个较长的串 \(B\) 的一个后缀 \(R\),那么和 \(B\) 相似的串 \(A\) 必然也有后缀 \(R\),且倒数第 \(|R|+1\) 个字母和 \(B\) 对应不同(防止算重),同时 \(\operatorname{LCP}(A,B) \leq |B|-|R|-K\)。那么我们考虑在两个串的 \(\operatorname{LCP}\) 处统计贡献。首先把所有串正反插入两个 Trie,遍历后缀 Trie 时把当前节点上的串按照大小排序,从小往大遍历即可。问题转化为前缀 Trie 上的单点加和求子树和,不难用 BIT 维护。
复杂度 \(O(\sum |S|\log n)\)。
宝宝选数字
给定两个长为 \(n\) 的数组 \(a,b\)。你需要按照任意顺序选择 \(k\) 个下标 \(i_1,\dots,i_k\),使得:
\[\sum_{j=1}^{k} a_{i_j}-\sum_{j=1}^{k-1}|b_{i_j}-b_{i_{j+1}}| \]最大。特别的,当 \(k=1\) 时后面的求和为 \(0\)。
\(n,k \leq 2 \times 10^5, a_i,b_i \leq 10^9\)。
首先按照 \(b\) 排序,记 \(f(l,r)\) 表示 \([l,r]\) 中最大的 \(k\) 个 \(a_i\) 之和加上 \(-b_r+b_l\),不难发现 \(f\) 满足四边形不等式。
所以若我们记 \(g_i=\max_{1 \leq j \leq i} f(j,i)\),则 \(g\) 具有决策单调性。
考虑分治解决,则我们需要一个支持快速删除一个数,恢复上一次操作和查询前 \(k\) 大的数据结构,而这链表可以做到:
int val[N],lim,sum;
struct node{
int x,y,id;
} a[N];
void ins(int x){
x=a[x].id; L[R[x]]=R[L[x]]=x;
if(x>lim) sum+=val[x]-val[lim],lim=R[lim];
}
void del(int x){
x=a[x].id; R[L[x]]=R[x]; L[R[x]]=L[x];
if(x>=lim) lim=L[lim],sum+=val[lim]-val[x];
}
复杂度 \(O(n\log n)\)。
三分图
定义一张无向图被称为三分图,当且仅当存在至少一种将图中每个顶点染成红、黄、蓝三色之一的方案,使得任意一条边的两个端点颜色不同。
定义一个大小为 \(n\) 的排列 \(p\) 构造的无向图 \(G(p)\) 为 \(\forall 1\leq i<j \leq n\),若 \(p_i>p_j\),则 \(i,j\) 之间有一条边。
给你两个大小为 \(n\) 的排列 \(p,q\),判断有多少个排列 \(k\) 满足:
- \(w\) 的字典序 \(\geq p\) 且 \(\leq q\)。
- \(G(w)\) 是三分图。
多组询问,\(n,T \leq 300\),答案对 \(998244353\) 取模。
发现排列 \(p\) 中若存在 \(a<b<c<d\) 满足 \(p_a>p_b>p_c>p_d\),则 \(G(p)\) 一定不是三分图。
故第二条限制转化为 \(\operatorname{LDS}(p) \leq 3\),由 Dilworth 定理,这等价于可以将排列划分为三个上升子序列。
先考虑怎么判断这个东西,我们可以维护三个上升子序列的末尾元素 \(c_1,c_2,c_3\),插入 \(p_k\) 时,找到最小的 \(c_p\) 满足 \(c_p < k\),然后将 \(c_p\) 修改为 \(k\)。若无法插入 \(p_k\),则不合法。
考虑 dp,设当前三个上升子序列的末尾元素为 \(c_1,c_2,c_3\),则此时 \(<c_1\) 的数一定都已经被插入过了,我们记 \(x_1=(c_1,c_2)\) 中剩多少个数没填 ,\(x2=(c_2,c_3)\) 中剩多少个数没填,\(x3=(c_3,n]\) 中剩多少个数没填,后继状态只依赖于 \(x_1,x_2,x_3\)。令 \(f_{x_1,x_2,x_3}\) 表示这种状态下的合法排列数量,则有转移:
不好优化,改为 dp \(g_{x_1+x_2+x_3,x_2+x_3,x_3}\),则对于 \(g_{a,b,c}\):
可以前缀和优化到 \(O(n^3)\)。
而对于字典序的限制,我们可以差分转化为计数字典序 \(\leq p\) 的方案数。套路地枚举所求排列和 \(p\) 的第一个不同位置,同时贪心维护 \(c_1,c_2,c_3\),查表 \(g\) 获取方案数累加即可。
非常厉害,总复杂度 \(O(n^3+Tn^2)\),可以通过。
NarrowRectangles
考虑设 \(dp_{i,j}\) 表示考虑前 \(i\) 个矩形,把第 \(i\) 个矩形左端点移到 \(j\) 的最小代价。
记 \(len_i\) 为第 \(i\) 个矩形长度,不难得到:
slope trick,每次相当于就是把原函数为 0 的一段往两边拉,加上一个绝对值函数。
由于 \(dp_i\) 图像斜率最低为 \(-i\),最高为 \(i\),所以两边移动的数量是确定的。
那么直接用可重集维护斜率变化点就好。
可重集需要支持查询极值,删除极值和加入元素,直接用堆实现就行,复杂度 \(O(n \log n)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=1e5+9,M=1e5+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,l[N],r[N],ans;
int len[N],tagl,tagr;
priority_queue<int> sl;
priority_queue<int,vector<int>,greater<int> > sr;
void Mian(){
cin>>n;
for(int i=1;i<=n;++i) cin>>l[i]>>r[i];
for(int i=1;i<=n;++i) len[i]=r[i]-l[i];
sl.push(l[1]); sr.push(l[1]);
tagl=tagr=0; ans=0;
for(int i=2;i<=n;++i){
tagl-=len[i],tagr+=len[i-1];
int L=sl.top()+tagl,R=sr.top()+tagr;
if(l[i]<L){
ans+=(L-l[i]);
sl.pop();
sl.push(l[i]-tagl); sl.push(l[i]-tagl);
sr.push(L-tagr);
}else if(l[i]>R){
ans+=(l[i]-R);
sr.pop();
sr.push(l[i]-tagr); sr.push(l[i]-tagr);
sl.push(R-tagl);
}else{
sl.push(l[i]-tagl); sr.push(l[i]-tagr);
}
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
烟花表演
设 \(f_{u,j}\) 为子树 \(u\) 内的烟花在 \(j\) 时刻引爆的最小调整代价,有:
考察一下这个 min 里面是在干什么,令 \(f'_{v,k}\) 是加上后面那个绝对值之后的新函数,\(L,R\) 为原函数斜率为 0 的那一段的左右端点,记 \(w=w(u,v)\),分类讨论一下:
然后考虑这样一个 \(f'_{v,k}\) 如何贡献到 \(f_{u,j}\)。把横轴改为 \(j\) 观察一下图像,发现关于原函数 \(f_{v,k}\) 其实就是把斜率 \(\leq -1\) 的部分值加上 \(w\),把 \([L,R]\) 段整体向右平移 \(w\),并在 \([L,L+w]\) 间插入一条斜率为 \(-1\) 的线段,然后把 \(R\) 后的部分修改为一条斜率为 \(1\) 的射线。
slope trick,把 \(R\) 后的部分修改为一条斜率为 \(1\) 的射线只需要弹出最大的 \(s-1\) 个斜率变化点,其中 \(s\) 为这个点的儿子数量,平移 \([L,R]\) 直接继续弹出两个点加上 \(w\) 再塞回去就行了。而值加上 \(w\) 和插入 \(-1\) 的线段其实不需要操作,因为没有产生新的斜率变化点。
最后考虑计算答案,一个显然的事情是 \(f_{1,0}\) 是容易计算的,为所有边权之和,那么接下来遍历一边可重集就能得到在最低点处的值了。
对于可重集,我们需要查询和删除极值,以及合并操作,为了保证复杂度正确,使用左偏树实现,复杂度 \(O((n+m) \log (n+m))\),别忘了需要开双倍空间。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=6e5+9,M=1e5+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,m,ans,a[N],fa[N],deg[N];
int val[N],rt[N],idx;
int ls[N],rs[N],d[N];
int merge(int u,int v){
if(!u || !v) return (u|v);
if(val[u]<val[v]) swap(u,v);
rs[u]=merge(rs[u],v);
if(d[ls[u]]<d[rs[u]]) swap(ls[u],rs[u]);
if(!rs[u]) d[u]=0;
else d[u]=d[rs[u]]+1;
return u;
}
int pop(int u){ return merge(ls[u],rs[u]);}
void Mian(){
cin>>n>>m;
for(int i=2;i<=n+m;++i){
cin>>fa[i]>>a[i];
deg[fa[i]]++; ans+=a[i];
}
for(int i=n+m;i>1;--i){
int l=0,r=0;
if(i<=n){
while(--deg[i]) rt[i]=pop(rt[i]);
r=val[rt[i]]; rt[i]=pop(rt[i]);
l=val[rt[i]]; rt[i]=pop(rt[i]);
}
val[++idx]=l+a[i]; val[++idx]=r+a[i];
rt[i]=merge(rt[i],merge(idx-1,idx));
rt[fa[i]]=merge(rt[fa[i]],rt[i]);
}
while(deg[1]--) rt[1]=pop(rt[1]);
for(int lst=rt[1],p=-1;rt[1];--p){
rt[1]=pop(rt[1]);
ans+=(val[lst]-val[rt[1]])*p;
lst=rt[1];
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
Ribbons on Tree
思考了一会发现可以设 \(dp_{u,j}\) 为 \(u\) 子树内有 \(j\) 个点要匹配,为了保证合法每次只能跨子树匹配,那么转移合并的时候就是枚举两边点数和匹配对数,有 \(dp_{u,i} \times dp_{v,j} \times \binom{i}{k} \times \binom{j}{k} \longrightarrow dp'_{u,i+j-2k}\),是 \(O(n^3)\) 的,无法通过。
考虑容斥,发现钦定某条边不选的情况是容易计算的,考虑 \(n-1\) 个条件形如第 \(i\) 条边被覆盖,设 \(f_S\) 为钦定 \(S\) 边集内的边不被覆盖下的匹配数,答案就为 $\sum_S (-1)^{|S|}f_S $。而一个边集内的边钦定不被覆盖相当于把原树分割成了若干连通块,一个大小为 \(n\) 的连通块的匹配数量 $g_n= {\textstyle \prod_{i=2}^{\frac{n}{2} } (i-1)} $ 是容易计算的,故设 \(dp_{u,j}\) 为 \(u\) 所在连通块大小为 \(j\) 时的方案数,把容斥系数带入 dp 方程,转移为 \(dp_{u,i}\times dp_{v,j} \times (-1) \times g_i \longrightarrow dp'_{u,i},dp_{u,i}\times dp_{v,j}\longrightarrow dp'_{u,i+j}\),即 \(u,v\) 间的边是否被钦定,此时答案即 \(\sum d p_{1,i} \times g_i\) 复杂度 \(O(n^2)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=5000+9,M=(1<<20)+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,f[N][N],g[N],ans;
int siz[N],tmp[N];
vector<int> G[N];
void dfs(int u,int fa){
f[u][1]=1;
siz[u]=1;
for(auto v:G[u]){
if(v==fa) continue;
dfs(v,u);
for(int i=0;i<=siz[u]+siz[v];++i) tmp[i]=0;
for(int i=0;i<=siz[u];++i){
for(int j=0;j<=siz[v];++j){
(tmp[i]+=(f[u][i]*f[v][j]%MOD*(-g[j]+MOD)%MOD))%=MOD;
(tmp[i+j]+=f[u][i]*f[v][j]%MOD)%=MOD;
}
}
for(int i=0;i<=siz[u]+siz[v];++i) f[u][i]=tmp[i];
siz[u]+=siz[v];
}
}
void Mian(){
cin>>n;
g[0]=1;
for(int i=2;i<=n;++i) g[i]=g[i-2]*(i-1)%MOD;
for(int i=1;i<n;++i){
int x,y; cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,0);
for(int i=0;i<=n;++i)
(ans+=(f[1][i]*g[i]%MOD))%=MOD;
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
按位或
好题。
全部出现的期望时间不太好求,但是发现元素首次出现的期望时间是好求的。
考虑 min-max 容斥,把一个二进制数看成一个集合,定义 \(max(S)\) 为 \(S\) 最后一个元素出现时所用次数, \(min(S)\) 为第一个元素出现时所有次数,有 \(E(max(S))=\sum_{T \subset S} (-1)^{(|T|+1)} E(min(S))\)。
接下来看看 \(min(S)\) 怎么算。记 \(P_S=1-\sum_{k \in \bar{S}} p_k\),其中 \(\bar{S}\) 为 \(S\) 的补集,即所有会贡献到集合 \(S\) 中的数出现概率之和。则 $$E(min(S))=1 \times P_S+2\times (1-P_S)\times P_S+3\times (1-P_S)^2\times P_S+...$$
那么只需要一次 FMT 这个题就做完了,复杂度 \(O(n2^n)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=20+9,M=(1<<20)+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,siz[M];
double p[N],f[M],ans;
void Mian(){
cin>>n;
for(int i=0;i<(1<<n);++i) cin>>f[i];
for(int i=0;i<n;++i){
for(int S=0;S<(1<<n);++S) if((S>>i)&1){
f[S]+=f[S^(1<<i)];
}
}
for(int S=0;S<(1<<n);++S) siz[S]=siz[S>>1]+(S&1);
for(int S=1;S<(1<<n);++S){
if(1-f[((1<<n)-1)^S]<eps) return puts("INF"),void();
if(siz[S]&1) ans+=(1.0/(1.0-f[((1<<n)-1)^S]));
else ans-=(1.0/(1.0-f[((1<<n)-1)^S]));
}
printf("%.15lf",ans);
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
异或图
哦哦,哦哦哦!
连通图限制很难处理,翻译一下把限制改为连通块数量为 1。\(n\) 极小,可以直接钦定最终的连通块(即钦定的连通块间一定不存在边,连通块内不做限制),相当于一个至少 \(k\) 个连通块的计数。这时我们知道哪些边一定不存在,等价于选出图的异或值在这些边上面为 0,问题转化为求一些异或方程解的数量,这个直接扔到线性基里面求出来一组线性无关向量个数就行了,贡献为 \(2^{s-t}\),其中 \(t\) 为线性基里向量数量。
接下来,考虑设 \(f_i\) 为恰好 \(i\) 个连通块的方案数, \(g_i\) 为不少于 \(i\) 个连通块的方案数,得到:
作斯特林繁衍,得到:
所求即为:
然后直接枚举算就行了,复杂度 \(O(Bell_n\times \operatorname{poly}(n,s))\),可以接受。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=10+9,M=60+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,s,a[M],g[N],ans;
int trs[N][N],idx;
string o[M];
int h[M],cnt;
void init(){
for(int i=0;i<=50;++i) h[i]=0;
cnt=0;
}
void ins(int x){
for(int i=50;i>=0;--i){
if(!((x>>i)&1)) continue;
if(h[i]){x^=h[i]; continue;}
h[i]=x; ++cnt; return ;
}
}
int scc,st;
vector<int> bel[N];
void calc(){
st=0;
for(int i=1;i<=scc;++i){
for(int j=i+1;j<=scc;++j){
for(auto u:bel[i]) for(auto v:bel[j])
st|=(1ll<<trs[u][v]);
}
}
init();
for(int i=1;i<=s;++i){
int now=a[i]&st;
ins(now);
}
g[scc]+=(1ll<<(s-cnt));
}
void dfs(int u){
if(u>n){
calc();
return ;
}
for(int i=1;i<=scc;++i){
bel[i].push_back(u);
dfs(u+1);
bel[i].pop_back();
}
++scc;
bel[scc].push_back(u);
dfs(u+1);
bel[scc].pop_back();
--scc;
}
int fac[N];
void Mian(){
cin>>s;
for(int i=1;i<=s;++i) cin>>o[i];
for(int i=1;i<=10;++i)
if(i*(i-1)==2*o[1].size()){n=i; break;}
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
trs[i][j]=trs[j][i]=idx++;
for(int k=1;k<=s;++k){
int pos=0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(o[k][pos++]-'0') a[k]|=(1ll<<trs[i][j]);
}
dfs(1);
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i;
for(int i=1;i<=n;++i){
if(i&1) ans+=fac[i-1]*g[i];
else ans-=fac[i-1]*g[i];
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
值得注意的是,设 \(f_i\) 为恰好 \(i\) 个连通块的方案数, \(g_i\) 为不少于 \(i\) 个连通块的方案数,得到的 \(f_1=\sum_{i=1}^{n} (-1)^{i-1} (i-1)! g_i\) 的式子在连通图计数中是具有普适性的。或者说几个式子都是普适的就对了。
虽然暂时还没做到下一个这样的题。
呃啊,都写得好长啊,而且感觉还没把思路说的很清楚,后面写的精简点吧。
Farthest City
\(d_n\) 最远启发我们按照距离分层 \(dp\)。
设 \(dp_{i,j}\) 表示考虑了 \(i\) 个点,其中最后一层点有 \(j\) 个,不难得到转移
答案就是 \(\sum_{i=1}^{n-1} f_{n-1,i} \times (2^i-1)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=500+9,M=60+9;
const int MOD=998244353,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,m,f[N][N];
int C[N][N],ans,pw2[N*N],coef[N][N];
void Mian(){
cin>>n>>m;
for(int i=0;i<=n;++i){
for(int j=0;j<=i;++j){
if(!j) C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%m;
}
}
pw2[0]=1;
for(int i=1;i<=n*n;++i) pw2[i]=pw2[i-1]*2%m;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
coef[i][j]=qpow(pw2[i]-1+m,j,m);
f[1][1]=1;
for(int i=2;i<=n;++i){
for(int j=1;j<i;++j){
for(int k=1;k<=i-j;++k){
f[i][j]+=f[i-j][k]*C[n-1-(i-j)][j]%m*coef[k][j]%m;
f[i][j]%=m;
}
f[i][j]=f[i][j]*pw2[C[j][2]]%m;
}
}
for(int i=1;i<=n-1;++i){
ans+=f[n-1][i]*(qpow(2,i,m)-1+m)%m;
ans%=m;
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
Inversion Sum
直接不太好搞,转一下期望就随便算了。
设 \(dp_{i,j}\) 表示最终 \(a_i<a_j\) 的概率,视每个操作有 \(\frac{1}{2}\) 概率执行,有转移:
复杂度 \(O(nq)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=3000+9,M=60+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,q,a[N],f[N][N],ans;
void Mian(){
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]=(a[i]<a[j]?1:0);
int INV2=qpow(2,MOD-2,MOD);
for(int i=1;i<=q;++i){
int x,y; cin>>x>>y;
f[x][y]=f[y][x]=(f[x][y]+f[y][x])%MOD*INV2%MOD;
for(int j=1;j<=n;++j) if(j!=x && j!=y){
f[x][j]=f[y][j]=(f[x][j]+f[y][j])%MOD*INV2%MOD;
f[j][x]=f[j][y]=(f[j][x]+f[j][y])%MOD*INV2%MOD;
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<i;++j)
(ans+=f[i][j])%=MOD;
ans=ans*qpow(2,q,MOD)%MOD;
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
kangaroo
神秘。
连续段 dp。题意转化后为固定首尾计数每个数都小于/大于左右两个的排列数量。设 \(dp_{i,j}\) 为考虑填入了 \(1,..,i\) 时有 \(j\) 个满足要求的连续段,有转移:
- 单独起一段,\(dp_{i,j}=(j-[i>s]-[i>t])\times dp_{i-1,j-1}\)
- 加到某一段首尾,由于此时填的数一定比任意一个数大,后面填的数又比当前数大,故一定会在加的这个数上出现不合法情况,此转移不存在。
- 合并两个段,\(dp_{i,j}=j\times dp_{i-1,j+1}\)。
对于 \(s,t\) 单独处理,为 \(dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}\),即接到当前首/尾段后或者在首尾新开一段,答案就是 \(f_{n,1}\)。
复杂度 \(O(n^2)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=2e3+9,M=60+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,s,t,f[N][N];
void Mian(){
cin>>n>>s>>t;
f[0][0]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(i==s || i==t){
f[i][j]=(f[i-1][j]+f[i-1][j-1])%MOD;
continue;
}
f[i][j]+=(j-(i>s?1:0)-(i>t?1:0))*f[i-1][j-1]%MOD;
f[i][j]+=j*f[i-1][j+1]%MOD;
f[i][j]%=MOD;
}
}
cout<<f[n][1];
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
Skills
icpc 济南这场怎么那么智慧。
先考虑一个简单 dp,设 \(dp_{i,p,j,k}\) 表示第 \(i\) 天学习了技能 \(p\),剩下两个技能按照标号大小排列分别有 \(j,k\) 天没学了,\(j,k\) 为 \(0\) 说明从未学过该技能。转移是简单的,复杂度 \(O(n^3)\),过不了。
注意到如果学了一个技能,那么我们一定不会让这个技能的熟练度降到 \(0\),又由于降熟练度的速度是在平方级别的,所以 \(j,k\) 的取值最大只到 \(\sqrt{V}\) 级别,复杂度来到 \(O(nV)\),可以通过。
滚动数组记得清空,,,
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=1000+9,M=1e4+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,a[N][3],f[2][3][209][209],ans;
void Mian(){
cin>>n;
for(int i=1;i<=n;++i)
for(int j=0;j<3;++j)
cin>>a[i][j];
for(int i=0;i<3;++i)
for(int j=0;j<=200;++j) for(int k=0;k<=200;++k)
f[0][i][j][k]=f[1][i][j][k]=-INF;
f[0][0][0][0]=f[0][1][0][0]=f[0][2][0][0]=0;
for(int u=0;u<n;++u){
int gt=u&1;
for(int j=0;j<=200;++j) for(int k=0;k<=200;++k){
chkmax(f[gt^1][0][j?j+1:0][k?k+1:0],f[gt][0][j][k]+a[u+1][0]-(j?j+1:0)-(k?k+1:0));
chkmax(f[gt^1][1][1][k?k+1:0],f[gt][0][j][k]+a[u+1][1]-1-(k?k+1:0));
chkmax(f[gt^1][2][1][j?j+1:0],f[gt][0][j][k]+a[u+1][2]-1-(j?j+1:0));
chkmax(f[gt^1][0][1][k?k+1:0],f[gt][1][j][k]+a[u+1][0]-1-(k?k+1:0));
chkmax(f[gt^1][1][j?j+1:0][k?k+1:0],f[gt][1][j][k]+a[u+1][1]-(j?j+1:0)-(k?k+1:0));
chkmax(f[gt^1][2][j?j+1:0][1],f[gt][1][j][k]+a[u+1][2]-(j?j+1:0)-1);
chkmax(f[gt^1][0][k?k+1:0][1],f[gt][2][j][k]+a[u+1][0]-(k?k+1:0)-1);
chkmax(f[gt^1][1][j?j+1:0][1],f[gt][2][j][k]+a[u+1][1]-(j?j+1:0)-1);
chkmax(f[gt^1][2][j?j+1:0][k?k+1:0],f[gt][2][j][k]+a[u+1][2]-(j?j+1:0)-(k?k+1:0));
}
for(int j=0;j<=200;++j)
for(int k=0;k<=200;++k)
for(int p=0;p<3;++p)
f[gt][p][j][k]=-INF;
}
ans=0;
for(int j=0;j<=200;++j)
for(int k=0;k<=200;++k)
for(int p=0;p<3;++p)
chkmax(ans,f[n&1][p][j][k]);
cout<<ans<<'\n';
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T; cin>>T;
while(T--){
Mian();
Mianclr();
}
}

浙公网安备 33010602011771号