IOI2020 集训队作业 Part 3
[AGC029F] Construction of a tree
[AGC030C] Coloring Torus
[AGC030D] Inversion Sum
[AGC030E] Less than 3
[AGC030F] Permutation and Minimum
把所有数(包括已经确定的)从大往小考虑。那么每一个 \(b\) 是什么就是看它对应的那一对什么时候被填完。
如果一对都已经有数了,没有意义,扔掉。
令 \(f_{i,j,k}\) 表示考虑了 \(i\) 到 \(2n\) 的数,填了一个数的对中,有 \(j\) 对是人为填了一个数,有 \(k\) 对是天然填了一个数。后两维是有区别的,因为后面那种是每对固定了位置,前面的没有。
注意到由于前面那种可以每对之间任排,所以就暂不考虑前面那种之间的顺序,答案为 \(f_{1,0,0}\times c!\)。
边界 \(f_{2n+1,0,0}=1\)。
如果是一个人为填进去的数,看看是新开一对,还是与另一个人为填的配,还是与天然填的配。\(f_{i,j,k}=f_{i+1,j-1,k}+f_{i+1,j+1,k}+(k+1)f_{i+1,j,k+1}\)。
如果是一个天然填进去的数,同样,注意不能和另一个天然填的配。\(f_{i,j,k}=f_{i+1,j,k-1}+f_{i+1,j+1,k}\)。
时间复杂度 \(O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=333,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,f[maxn*2][maxn][maxn],cnt1,cnt2,ans;
bool vis[maxn*2],ind[maxn*2];
int main(){
n=read();
FOR(i,1,n){
int x=read(),y=read();
if(~x && ~y) vis[x]=vis[y]=true;
else if(~x) ind[x]=true,cnt2++;
else if(~y) ind[y]=true,cnt2++;
else cnt1++;
}
f[2*n+1][0][0]=1;
ROF(i,2*n,1) FOR(j,0,cnt1+cnt2) FOR(k,0,cnt2){
if(vis[i]) f[i][j][k]=f[i+1][j][k];
else if(ind[i]){
if(k) f[i][j][k]=(f[i][j][k]+f[i+1][j][k-1])%mod;
if(j!=cnt1+cnt2) f[i][j][k]=(f[i][j][k]+f[i+1][j+1][k])%mod;
}
else{
if(j) f[i][j][k]=(f[i][j][k]+f[i+1][j-1][k])%mod;
if(j!=cnt1+cnt2) f[i][j][k]=(f[i][j][k]+f[i+1][j+1][k])%mod;
if(k!=cnt2) f[i][j][k]=(f[i][j][k]+1ll*(k+1)*f[i+1][j][k+1])%mod;
}
}
ans=f[1][0][0];
FOR(i,1,cnt1) ans=1ll*ans*i%mod;
printf("%d\n",ans);
}
[AGC031D] A Sequence of Permutations
[AGC031E] Snuke the Phantom Thief
[AGC031F] Walk on Graph
[AGC032C] Three Circuits
[AGC032D] Rotation Sort
[AGC032E] Modulo Pairing
[AGC032F] One Third
[AGC033D] Complexity
[AGC033E] Go around a Circle
[AGC033F] Adding Edges
[AGC034D] Manhattan Max Matching
[AGC034E] Complete Compress
[AGC034F] RNG and XOR
[AGC035C] Skolem XOR Tree
[AGC035D] Add and Remove
留下的卡肯定是第一张和最后一张。
最后答案可以表示成 \(\sum a_ik_i\) 的形式。下称 \(k_i\) 为贡献系数。
考虑把整个过程倒过来,一开始只有第一张和最后一张卡(但是权值不是输入给出的那个),然后每次中间长出一张卡(权值也不是输入给出的),两边的权值减掉中间那个卡的权值。
\(f_{l,r,x,y}\) 表示第 \(l\) 张卡和第 \(r\) 张卡相邻,\(k_l=x,k_r=y\),这时的最小值。
答案即为 \(f_{1,n,1,1}\)。
边界,\(l+1=r\) 时,\(f_{l,r,x,y}=xa_l+ya_r\)。
转移,枚举中间长出的是第 \(i\) 张卡。那么 \(k_i=x+y\)。
方程 \(f_{l,r,x,y}=\min\limits_{l+1\le i\le r-1}(f_{l,i,x,x+y}+f_{i,r,x+y,y}-a_i(x+y))\)。
虽然看起来状态数很多,但是如果我们固定看第三、四维,实际上只有 \(2^n\) 种,要么往左就肯定是 \((x,x+y)\),要么往右就肯定是 \((x+y,y)\)。
此时有时间复杂度 \(O(2^nn^3)\) 带比较小的常数。据说可以继续分析到 \(O(2^nn)\)。
由于我比较菜,弄了一堆组合式子算出来个 \(O(3^nn)\),就不拿出来丢人现眼了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<PII,PII> st;
const int maxn=100010;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,a[maxn];
vector<ll> dp[20][20];
ll DP(int l,int r,int x,int y,int t){
if(l+1==r) return 1ll*a[l]*x+1ll*a[r]*y;
ll &d=dp[l][r][t];
if(d) return d;
ll s=1e18;
FOR(i,l+1,r-1) s=min(s,DP(l,i,x,x+y,t<<1)+DP(i,r,x+y,y,t<<1|1)-1ll*a[i]*(x+y));
return d=s;
}
int main(){
n=read();
FOR(i,1,n) a[i]=read();
FOR(i,1,n) FOR(j,i+1,n) dp[i][j].resize(1<<(n-j+i));
printf("%lld\n",DP(1,n,1,1,1));
}
[AGC035E] Develop
[AGC035F] Two Histograms
[AGC036D] Negative Cycle
[AGC036E] ABC String
[AGC036F] Square Constraints
[AGC037D] Sorting a Grid
为了方便,给每个数染上它最后应该在的那个行的编号的颜色,共 \(n\) 种颜色。
在第三次重排前,一定有每一行已经有了它该有的那些数。
在第二次重排前,一定有每一列都有每一种颜色恰好一个。
所以第一次重排,就是要让每一列都有每种颜色恰好一个。
确定了第一次重排的方案,第二次重排就很简单了,列内按颜色排序即可。
考虑从左往右一列一列地填第一次重排完后的矩阵。在填一列的时候,就是要让每一行能和每一种颜色一一配对。
那么如果一行中有一种颜色,就把它和这种颜色连边。出现多次就连多条边。
网络流求出这个二分图的完美匹配,就能知道这一列怎么填了。把这些匹配边删掉然后填下一列。
关于每次都能找到完美匹配的正确性证明:
Hall 定理:一个两部点数相同的二分图有完美匹配,当且仅当对于左点集的任意一个子集,出边连向的右边点的并集大小大于等于该点集大小。
必要性显然,下证充分性。
考虑它的最大匹配,若存在一个点 \(u\) 没有匹配上,由前提一定有至少一个与它相连的点。
如果这些点中有没被匹配的,那么它们是可以匹配的,与最大匹配矛盾。
否则,设这连向的点有 \(x\) 个,把这 \(x\) 个点对应的匹配点也拎出来。
考虑这些点和 \(u\) 共 \(x+1\) 个点,由前提一定至少有 \(x+1\) 个与它们相连的点。
由于和 \(u\) 相连的点都已经在相连点集中了(只有 \(x\) 个),所以剩下的那些点一定是与除了 \(u\) 的其它 \(x\) 个点相邻。
如果那些点有没被匹配的,那么存在增广路,矛盾。否则继续连,继续……
最后会全部左点都在里面,就只能矛盾了。
用它来证这题就很简单了:在填第 \(i\) 列的时候,左边右边每个点度数都是 \(m-i+1\),所以对于左边大小为 \(s\) 的点集,与它相连的右边点集度数和至少是 \(s(m-i+1)\),大小也肯定至少是 \(s\)。
时间复杂度 \(O(nm^2\sqrt{n})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=222,maxm=22222;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],tmp[maxn];
int s,t,q[maxn],h,r,dis[maxn],el,head[maxn],cur[maxn],to[maxm],nxt[maxm],w[maxm];
bool use[maxm];
inline void add(int u,int v,int w_){
to[++el]=v;nxt[el]=head[u];head[u]=el;w[el]=w_;
to[++el]=u;nxt[el]=head[v];head[v]=el;w[el]=0;
}
bool bfs(){
MEM(dis,-1);
q[h=r=1]=s;
dis[s]=0;
while(h<=r){
int u=q[h++];
for(int i=head[u];i;i=nxt[i]) if(w[i]){
int v=to[i];
if(dis[v]==-1) dis[v]=dis[u]+1,q[++r]=v;
}
}
return ~dis[t];
}
int dfs(int u,int minres){
if(u==t || !minres) return minres;
int f,rtf=0;
for(int &i=cur[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]==dis[u]+1 && (f=dfs(v,min(minres,w[i])))){
minres-=f;rtf+=f;
w[i]-=f;w[i^1]+=f;
if(!minres) break;
}
}
return rtf;
}
int main(){
n=read();m=read();
FOR(i,1,n) FOR(j,1,m) a[i][j]=read();
s=2*n+1;t=2*n+2;
FOR(i,1,m){
FOR(i,1,2*n+2) head[i]=0;
FOR(i,1,el) to[i]=nxt[i]=w[i]=0;
el=1;
FOR(i,1,n) add(s,i,1),add(i+n,t,1);
FOR(i,1,n) FOR(j,1,m) if(!use[a[i][j]]) add(i,(a[i][j]+m-1)/m+n,1);
int f=0;
while(bfs()){
FOR(i,1,2*n+2) cur[i]=head[i];
f+=dfs(s,1e9);
}
assert(f==n);
FOR(j,1,n) for(int e=head[j];e;e=nxt[e]) if(!w[e]){
int tp=to[e]-n;
FOR(k,1,m) if((a[j][k]+m-1)/m==tp && !use[a[j][k]]){
use[b[j][i]=a[j][k]]=true;break;
}
}
}
FOR(i,1,n){
FOR(j,1,m) printf("%d ",b[i][j]);
puts("");
}
FOR(j,1,m){
FOR(i,1,n) tmp[i]=b[i][j];
sort(tmp+1,tmp+n+1);
FOR(i,1,n) c[i][j]=tmp[i];
}
FOR(i,1,n){
FOR(j,1,m) printf("%d ",c[i][j]);
puts("");
}
}
[AGC037E] Reversing and Concatenating
[AGC037F] Counting of Subarrays
[AGC038E] Gachapon
[AGC038F] Two Permutations
[AGC039D] Incenters
[AGC039E] Pairing Points
以下令 \(n\) 为真正的总点数。
枚举 \(1\) 和哪个点配,设为 \(i\)。
现在是要:在 \([2,i)\) 中选一些点 \(p_1<p_2<\dots<p_x\),和 \((i,n]\) 中选一些点 \(q_1>q_2>\dots>q_x\),\(p_i\) 和 \(q_i\) 连线,对于剩下那些没被选到的点,之间连,的合法方案数。
令这个为 \(f_{l,i,r}\),要求即为所有 \(f_{2,i,n}\) 的和。边界 \(l=i=r\) 方案数是 \(1\)。
枚举 \(p_1\) 和 \(q_1\),设为 \(j\) 和 \(k\)。
注意到 \((j,i)\) 中连出去的边中,只可能是 \([l,j)\) 和 \((i,k)\)。且应该存在一个 \(p\) 使得 \(p\) 和之前的都连向 \([l,j)\),\(p+1\) 和之后的都连向 \((i,k)\)。
另一边同理,设分界点为 \(q\)。
注意到 \([l,j)\) 和 \((j,p]\) 连出的线不可能离开这一段,所以这里的方案数是 \(f_{l,j,p}\)。
另一边同理,\(f_{q,k,r}\)。
对于下面的 \([p+1,i)\) 和 \((i,q-1]\) 也是一样,\(f_{p+1,i,q-1}\)。
所以转移方程是 \(f_{l,i,r}+=\sum f_{p+1,i,q-1}f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\)。
转移顺序是按 \(r-l+1\) 从小到大枚举。
时间复杂度 \(O(n^7)\),虽然 \(n\le 40\),由于常数极其优秀已经可以通过。
(看转移条件大概就能看出来了:\(l\le j\le p<i<q\le k\le r\),\(r-l+1\) 为奇数,\(a_{j,k}=1\))
但是这个是可以继续优化的。
仍然按 \(r-l+1\) 从小到大枚举。然后里面先枚举 \(p,q\)。
注意到如果枚举了 \(i\),那么 \(f_{l,i,r}+=f_{p+1,i,q-1}\sum f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\),后面和 \(i\) 无关。
设这个为 \(s\)。考虑如何快速计算 \(s\)。
令 \(sum_{l,k,p}=\sum f_{l,j,p}[a_{j,k}=1]\)。那么 \(s=\sum sum_{l,k,p}f_{q,k,r}\)。
在适当的时候更新 \(sum\) 即可。
时间复杂度 \(O(n^5)\),常数依然小的可怕。经过测试如果改成取模,能跑 \(n\le 100\)(这里的 \(n\) 是新的 \(n\))。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=44;
#define MP make_pair
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
int x=0,f=0;char ch=getchar();
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,a[maxn][maxn];
char s[maxn];
ll f[maxn][maxn][maxn],fs[maxn][maxn][maxn],ans;
int main(){
n=read()*2;
FOR(i,1,n){
scanf("%s",s+1);
FOR(j,1,n) a[i][j]=s[j]-'0';
}
if(n==2) return printf("%d\n",a[1][2]),0;
FOR(i,2,n){
f[i][i][i]=1;
FOR(j,2,n) if(a[i][j]) fs[i][j][i]+=f[i][i][i];
}
FOR(len,3,n-1) if(len%2==1) FOR(l,2,n-len+1){
int r=l+len-1;
FOR(i,l,r) FOR(j,2,n) if(a[i][j]) fs[l][j][r]-=f[l][i][r];
FOR(p,l,r) FOR(q,p+1,r){
ll s=0;
FOR(k,q,r) s+=fs[l][k][p]*f[q][k][r];
FOR(i,p+1,q-1) f[l][i][r]+=s*f[p+1][i][q-1];
}
FOR(i,l,r) FOR(j,2,n) if(a[i][j]) fs[l][j][r]+=f[l][i][r];
}
FOR(i,2,n) if(a[1][i]) ans+=f[2][i][n];
printf("%lld\n",ans);
}