24钉耙第10场个人总结
1001
根据dilworth定理转化场上想到了,但是以为这东西能dp,然后怒写1h没过样例。
拆成 \(k\) 条反链后效性太强,可以看做dag上不重复计算点贡献的dp,好像就和原问题没区别了……
很多状态转移搞不太定的东西,其实可以想一想放图上搞,毕竟某些最优化dp就是一种最短路之类的图上问题。
本题里转化到费用流就一目了然,类似网络流24题里有一个的拆点方法,把覆盖问题转化成选择问题,那么只需要分别跑最大流是 \(1\sim n\) 的mfmc即可。
不会写原始对偶或消圈,留坑回头补。
1002
水题,但是和标算不一样,记录一下。
场上猜结论是从后往前一定是放了一段前缀和后缀,换句话讲到每个元素时能放的位置一定是一段连续区间。
第一种想法是类似题解的看第 \(i\) 个元素能放到的最好位置,但是我场上没这样想。
远古时期的记忆让我想起来有单调性的东西能把某个维度和dp值交换,于是我设计的状态是 \(f_{l,r}\) 表示当前空位置区间是 \([l,r]\) 能从后往前放的最多元素个数,转移把第 \(n-f_{l,r}\) 个元素放在最左或最右更新最大dp值即可。
复杂度是优秀的 \(O(m^2+n)\),但 \(n\leq m\),\cf\cf\cf。
1003
子集DP好题。
第一步转化就没想到:考虑把点编号变成该点拓扑序,那么每条边都是从小点到大点,那么对于某个点只需要考虑之前填过的点中不能有编号比它小的。
层之间的转移时简单的,难点在于层内钦定若干点颜色相同的转移。
一种方法是用高维前缀和,我们要求强制从小到大放这些颜色相同的点,有一维无形的状态是当前已经考虑了编号前若干小的点。
那么我们转移就需要正着做,如下:
for(int i=1; i<=n; ++i){
for(int j=0; j<(1<<n); ++j) f[i][j]=f[i-1][j];
for(int k=0; k<n; ++k){
int cur=id[k];
for(int j=0; j<(1<<n); ++j) if(((j>>cur)&1)&&((j&to[cur])==0)) add(f[i][j], f[i][j^(1<<cur)]);
}
for(int j=0; j<(1<<n); ++j) del(f[i][j], f[i-1][j]);
}
其中 \(to_i\) 表示能到达 \(i\) 的点集。
这里 \(f_{p,s}\) 的含义就变为了前缀和的含义,带上无形的状态就相当于 \(f_{p,s}=g_{p,i,s}+g_{p,i,s\setminus \{i\}}\),这样就保证了同层内没有边相连。
因为这会存在这层一个点都不选的转移,所以最后需要减掉自身。
这样复杂度就优化为了 \(O(2^nn^2)\),总复杂度就是 \(O(2^nn^2+qn)\)(这居然能过 \(n=20\))。
代码被hdu吞了,补档:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
struct IO{
static const int S=1<<21;
char buf[S],*p1,*p2;int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
template<typename T>inline IO&operator >> (T&x){
x=0;bool f=0;char ch=gc();
while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
f?x=-x:0;return *this;
}
inline IO&operator << (const char c){pc(c);return *this;}
template<typename T>inline IO&operator << (T x){
if(x<0) pc('-'),x=-x;
do{st[++st[0]]=x%10,x/=10;}while(x);
while(st[0]) pc('0'+st[st[0]--]);return *this;
}
}fin,fout;
const int mod=1e9+7;
inline ll fpow(ll x, ll y){
ll ret=1;
while(y){
if(y&1) ret=ret*x%mod;
x=x*x%mod; y>>=1;
}
return ret;
}
inline int plu(int x, int y){x+=y; return x>=mod?x-mod:x;}
inline int sub(int x, int y){x-=y; return x<0?x+mod:x;}
inline void add(int &x, int y){x+=y; if(x>=mod) x-=mod;}
inline void del(int &x, int y){x-=y; if(x<0) x+=mod;}
int T, n, m, q;
bool e[21][21];
int deg[21];
int que[21], hh, tt;
int id[21];
int f[21][(1<<20)+5];
int to[21];
int C[21];
char s[21];
int main(){
// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
fin>>T;
while(T--){
fin>>n; fin>>m; fin>>q;
memset(e, 0, sizeof e); memset(deg, 0, sizeof deg); memset(to, 0, sizeof to);
for(int i=1, x, y; i<=m; ++i){
fin>>x; fin>>y; --x; --y;
if(e[x][y]) continue;
e[x][y]=1; ++deg[y]; to[y]|=1<<x;
}
hh=0; tt=-1;
for(int i=0; i<n; ++i) if(!deg[i]) que[++tt]=i;
while(hh<=tt){
int x=que[hh]; id[hh]=x; ++hh;
for(int y=0; y<n; ++y) if(e[x][y]){
--deg[y]; if(!deg[y]) que[++tt]=y;
}
}
f[0][0]=1;
for(int i=1; i<=n; ++i){
for(int j=0; j<(1<<n); ++j) f[i][j]=f[i-1][j];
for(int k=0; k<n; ++k){
int cur=id[k];
for(int j=0; j<(1<<n); ++j) if(((j>>cur)&1)&&((j&to[cur])==0)) add(f[i][j], f[i][j^(1<<cur)]);
}
for(int j=0; j<(1<<n); ++j) del(f[i][j], f[i-1][j]);
}
while(q--){
int k; fin>>k; char cur=fin.gc();
while(cur!='0'&&cur!='1') cur=fin.gc();
int msk=0; for(int i=0; i<n; ++i) {
if(cur=='1') msk|=1<<i;
cur=fin.gc();
}
C[0]=1;
for(int i=1; i<=min(n, k); ++i) C[i]=fpow(i, mod-2)*C[i-1]%mod*(k-i+1)%mod;
int ans=0;
for(int i=0; i<=min(n, k); ++i) add(ans, (ll)C[i]*f[i][msk]%mod);
fout<<ans; fout.pc('\n');
}
}
return 0;
}
1004
令 \(f_{i,j}\) 表示用大小为 \(i\times j\) 的矩形能够最多覆盖的0的数量。
先做第一个单调性分析:\(f_{i,j}\geq f_{i+1, j},f_{i,j}\geq f_{i,j+1}\),这是因为能用 \((i+1)\times j\) 覆盖的位置一定能被 \(i\times j\) 覆盖,后者同理。
所以符合要求的 \((i,j)\) 对在整个 \(f\) 矩阵中排列成阶梯型,我们只需要求出阶梯的轮廓线即可。
接下来考虑一个 \(f_{i,j}\) 怎么在低于 \(O(n,m)\) 的时间求出。
考虑一个矩形至少会覆盖一个编号为 \(i\) 的倍数行,所以只需要枚举这些行的每一个位置,记录一下这个位置如果被包含在某个矩形内,矩形的上下边界范围。
于是需要提前预处理某个位置向上/下最多延伸的位置,求答案用区间rmq得到上下界,这里使用单调队列即可。
但这里得到的只是矩形右端点在此处的上下界,还需要再单调队列一遍求出只考虑包含在矩形里的上下界,倒过来再做一遍上下界的区间rmq即可,仍然可以单调队列。
求答案注意不能重复覆盖,要对当前上界和上一层下界分类讨论。
所以只需要 \(O(\frac{n}{i}m)\) 时间求出 \(f_{i,j}\) 的答案,同样可以整体翻转做到 \(O(\frac{m}{j}n)\),两者直接肯定取最优。
所以我们只需要 \(O(\sum_{(i,j)\in 轮廓线}\frac{nm}{max(i,j)})\) 的复杂度,这个数量级上界是 \(O(nm\ln(nm))\) 的。
1007
(怀疑数据有问题,蹲)
最简单的情况是不在同一联通块,只需要考虑联通块大小。
次简单的是树,只需要考虑路径奇偶性判断输赢。
然后就是有环的情况,显然一个人走到环上后肯定不会输,所以先看是否能走到环上,再看能否妨碍对手走到环上。
先搜索一遍去掉1度点,那么剩下的就都是环上的点了。
先考虑A和B离得最近的环上的点(后面称为fa和fb)不一样的情况:
对于A,要堵上B,首先要满足到fb的距离小于等于B到fb的距离,然后要考虑奇偶性,如果距离是奇数那么B恰好让A步步后退;对于B同理。
再考虑fa和fb一样的情况:
存在子树包含关系的情况同样只需考虑奇偶性,不过注意结局只有顶上的赢或平局。
不存在包含关系,则让二者同步爬升(当然不用真的去模拟)到存在包含关系后归到上一类讨论即可。
综上,只需要额外求一下1到所有点和fa到所有点,长度为奇/偶的最短路,只需要01bfs即可。
我懒了写的并查集,复杂度是 \(O(n+n\alpha (n))\)。
upd
我的问题,数据没问题,我唐了。
少考虑了在1的根存在一个很小的奇环,使得路径奇偶性改变的情况,多讨论这个就过了……
1010
考虑最低位不受前面位的影响,而且只考虑最低位时只需要异或运算,那么就可以得到所有答案的最低位。
再考虑次低位,发现只会收到最低位的进位的影响,所以只需要额外算上最低位的进位即可。
由此只需要从低到高依次考虑即可。
1012
考虑有 \(k\) 个空缺,那么这个置换就是由若干置换环和 \(k\) 条置换链组成的,我们需要做的就是对于 \(k!\) 种置换链分组的方式计算答案。
考虑对 \(2^k-1\) 种子集分别求出来颜色数,然后去做子集dp。
具体的,对于所有颜色数相同的子集,我们设计 \(f_{i,s}\) 表示当前生成了 \(i\) 个置换环,使用的置换链组成的集合是 \(s\) 的方案,转移使用子集枚举(只枚举当前状态补集的所有子集),复杂度可以做到均摊 \(O(3^kk)\)。
对于某个状态 \(f_{i,s}\) ,使用 \(\complement_{U}{s}\) 里面的元素仍然可能得到颜色数是当前所枚举的置换环。
一种思路是求至少有 \(i\) 个置换环的方案数,再二项式反演得到恰好的。
具体的,让 \(f_i\) 是恰好的方案数,那么 \(g_n=\sum_{i=n}^{k}C_{i}^nf_i\),同时有 \(g_n=\sum_{s}f_{n,s}*(k-|s|)!\),直接做就好了。
总体来看,时间复杂度是 \(O(\frac{n2^k}{w}+3^kk+nk^2)\)。