OpenCup 乱做
OpenCup 牛啊,下文默认 Div.1。
Ancient Magic Circle in Teyvat
Source: XXII Open Cup, GP of Nanjing K
给定一个 \(n\) 个点的完全图,有 \(m\) 条边是红色的,其余边是蓝色的,求出边均为蓝色的大小为 \(4\) 的完全子图个数与边均为红色的大小为 \(4\) 的完全子图个数的差。
\(4\le n\le 10^5\),\(0\le m\le \min(\frac{n(n-1)}{2},2\times 10^5)\)。
Solution
考虑容斥,首先对 \(n\) 个点的完全图进行一个号的标:
记 \(U\) 为所有的四元组,\(A_i\) 表示第 \(i\) 条边为红的四元组个数,考虑容斥原理:
发现当 \(k\) 等于 \(6\) 时,可以单独划分出来,所以式子可以变成:
左边式子的绝对值就是我们要求的,那么考虑求右边。
右边的所有式子可以分开来变成若干种不同的计数对象,如下(图是盗出题人的),其中红边表示必须是红,蓝边表示可以是红或是蓝:
最后一个做不了,但是其他的都可以利用统计三元环/四元环个数在 \(O(m\sqrt{m})\) 的复杂度内解决,那么这题就做完了。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=5e5;
int n,m;
ll res[12],cnt[N+10];
vector<pii> G[N+10],GG[N+10];
int vis[N+10];
bool cmp(int u,int v) {
if(G[u].size()==G[v].size()) return u<v;
return G[u].size()<G[v].size();
}
ll C(int n,int m) {
if(n<m) return 0;
if(m==2) return 1ll*n*(n-1)/2;
if(m==3) return 1ll*n*(n-1)/2*(n-2)/3;
if(m==4) return 1ull*n*(n-1)/2*(n-2)/3*(n-3)/4;
return 114514;
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1,x,y;i<=m;i++) {
scanf("%d %d",&x,&y);
G[x].pb({y,i}),G[y].pb({x,i});
}
res[1]=C(n,4),res[2]=m*C(n-2,2);
for(int u=1;u<=n;u++) res[3]=C(G[u].size(),2)+res[3];
res[4]=C(m,2)-res[3];
res[3]*=(n-3);
for(int u=1;u<=n;u++) res[5]+=C(G[u].size(),3);
for(int u=1;u<=n;u++)
for(pii i:G[u]) if(u<i.fi)
res[6]+=1ll*(G[u].size()-1)*(G[i.fi].size()-1);
for(int u=1;u<=n;u++)
for(pii i:G[u])
if(cmp(u,i.fi)) GG[u].pb(i);
for(int u=1;u<=n;u++) {
for(pii i:GG[u]) vis[i.fi]=i.se;
for(pii i1:GG[u])
for(pii i2:GG[i1.fi]) if(vis[i2.fi]) {
res[7]++,res[8]+=G[u].size()+G[i1.fi].size()+G[i2.fi].size()-6;
cnt[i1.se]++,cnt[i2.se]++,cnt[vis[i2.fi]]++;
}
for(pii i:GG[u]) vis[i.fi]=0;
}
for(int i=1;i<=m;i++) res[10]+=C(cnt[i],2);
res[6]-=3*res[7],res[7]*=n-3;
for(int u=1;u<=n;u++) {
for(pii i1:G[u])
for(pii i2:GG[i1.fi]) {
res[9]+=vis[i2.fi];
if(cmp(u,i2.fi)) vis[i2.fi]++;
}
for(pii i1:G[u])
for(pii i2:GG[i1.fi])
if(cmp(u,i2.fi)) vis[i2.fi]=0;
}
ll ans=res[1]-res[2]+res[3]+res[4]-res[5]-res[6]-res[7]+res[8]+res[9]-res[10];
printf("%lld\n",abs(ans));
}
Puzzle in Inazuma
Source: XXII Open Cup, GP of Nanjing B
给定一个 \(n\) 个点的带权完全图 \(G\),现在你可以对这个图做至多 \(10^5\) 次如下操作,使其变成另一张带权完全图 \(H\):
- 选取四个点 \(a,b,c,d\) 与权值 \(x\),使得边 \((a,b),(a,c),(a,d)\) 加上 \(x\),边 \((b,c),(b,d),(c,d)\) 减去 \(x\)。
构造一组方案或判断无解。
\(4\le n\le 100\),边权在 \(-100\sim 100\) 之间。
Solution
发现操作的权值对总权值是不变的,这是一个判无解的情况。
先让 \(G\) 的边权对 \(H\) 做差,将操作目标转变为使得 \(G\) 边权为 \(0\)。
接下来我们先来讨论 \(n\ge 6\) 的部分,首先,我们先称操作 \((a,b,c,d,x)\) 对应原题所述的操作:
考虑如下操作组:
- \((d,a,b,e,-x)\);
- \((d,a,b,c,x)\);
- \((e,a,c,d,x)\);
- \((e,a,b,c,-x)\)。
这样的操作组对应着将 \((a,b),(a,e)\) 添加 \(x\),将 \((a,c),(a,d)\) 减少 \(x\),运用类似的方式,我们就可以添加一个新点 \(f\),操作 \((a,b),(a,e)\) 减 \(x\),\((a,c),(a,f)\) 加 \(x\),也就是说,我们可以运用两组操作做到 \((a,d)\) 减少 \(x\),\((a,f)\) 增大 \(x\)。
这是一个一般的形式,我们可以通过这个方式直接构造出答案,所以 \(n\ge 6\) 时只要总权值相等就可以构造。
当 \(n=5\) 的时候,发现找不到 \(f\),考虑修改一波做法,我们发现可以 \(\text{swap}(c,e)\) 然后实现 \((a,b)\) 添加 \(2\times x\),\((a,e)\) 减少 \(2\times x\) 的操作。
那么能不能构造就取决于边权的奇偶性了,注意一次操作是可以改变 \(6\) 条边的奇偶性的,暴力枚举这 \(6\) 条边不和哪个点有关就可以做到 \(O(2^n)\) 的优秀复杂度。
当 \(n=4\) 的时候,操作恒定,可以直接列方程解。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=100;
int n,G[N+10][N+10],G1[N+10][N+10],T[N+10][N+10];
struct node {int a,b,c,d,e;};
vector<node> ans;
void NoAnswer() {
puts("-1");exit(0);
}
namespace n4 {
void work(int a,int b,int c,int d,int x) {ans.pb({a,b,c,d,x});}
void solve() {
int sum=G[1][2]+G[1][3]+G[1][4];
if(abs(sum-G[1][2])%2) NoAnswer();
if(abs(sum-G[1][3])%2) NoAnswer();
if(abs(sum-G[1][4])%2) NoAnswer();
if(G[1][3]+G[2][4]!=0) NoAnswer();
if(G[1][4]+G[2][3]!=0) NoAnswer();
if(G[1][2]+G[3][4]!=0) NoAnswer();
work(2,1,3,4,(sum-G[1][2])/2);
work(3,1,2,4,(sum-G[1][3])/2);
work(4,1,2,3,(sum-G[1][4])/2);
}
}
namespace n5 {
void add(int i,int j,int x) {
G[i][j]+=x,G[j][i]+=x;
}
void add(int a,int b,int c,int d,int x) {
add(a,b,x),add(a,c,x),add(a,d,x);
add(b,c,-x),add(b,d,-x),add(c,d,-x);
ans.pb({a,b,c,d,x});
}
void work(int a,int b,int c,int d,int e,int x) {//Make a->b,a->e plus x a->c a->d decrease x
add(d,a,b,e,-x),add(d,a,b,c,x);
add(e,a,c,d,x),add(e,a,b,c,-x);
}
void solve(int A,int D,int B,int x) {
int C=1,E=1;
while(C==A||C==D||C==B) ++C;
while(E==A||E==B||E==C||E==D) ++E;
work(A,B,C,D,E,x),work(A,B,D,E,C,x);
}
void solve() {
for(int S=0;S<(1<<n);S++) {
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
T[i][j]=0;
for(int i=1;i<=n;i++) if(S&(1<<(i-1))) {
for(int j=1;j<n;j++)
for(int k=j+1;k<=n;k++)
if(i!=j&&i!=k) T[j][k]^=1;
}
bool FL=1;
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
if(T[i][j]!=abs(G[i][j])%2) FL=0;
if(!FL) continue;
for(int i=1;i<=n;i++)
if(S&(1<<(i-1))) {
int a,b,c,d;
if(i==1) a=2,b=3,c=4,d=5;
if(i==2) a=1,b=3,c=4,d=5;
if(i==3) a=1,b=2,c=4,d=5;
if(i==4) a=1,b=2,c=3,d=5;
if(i==5) a=1,b=2,c=3,d=4;
add(a,b,c,d,1);
}
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++) {
if(G[i][j]==0) continue;
solve(j,i,j==n?i+1:n,G[i][j]/2);
}
}
}
}
namespace nn {
void add(int i,int j,int x) {
G[i][j]+=x,G[j][i]+=x;
}
void add(int a,int b,int c,int d,int x) {
add(a,b,x),add(a,c,x),add(a,d,x);
add(b,c,-x),add(b,d,-x),add(c,d,-x);
ans.pb({a,b,c,d,x});
}
void work(int a,int b,int c,int d,int e,int x) {//Make a->b,a->e plus x a->c a->d decrease x
add(d,a,b,e,-x),add(d,a,b,c,x);
add(e,a,c,d,x),add(e,a,b,c,-x);
}
void solve(int x) {
for(int i=2;i<=n;i++) if(G[x][i]) {
int tt[3],tot=0;
for(int j=1;j<=n;j++)
if(j!=1&&j!=i&&j!=x) {
tt[tot++]=j;
if(tot==3) break;
}
int tmp=G[x][i];
work(x,tt[0],tt[1],i,tt[2],tmp);
work(x,tt[0],tt[1],1,tt[2],-tmp);
}
}
struct node {int d,id;} a[N+10];
bool cmp(node x,node y) {return x.d<y.d;}
void solve() {
for(int i=2;i<=n;i++) solve(i);
// cerr<<"FUCK"<<endl;
for(int i=2;i<=n;i++) a[i]={G[1][i],i};
sort(a+2,a+n+1,cmp);
// cerr<<"FUCK"<<endl;
int L=2,R=n;
while(L<R) {
int tt[3],tot=0;
for(int j=1;j<=n;j++) {
if(j!=1&&j!=a[L].id&&j!=a[R].id) tt[tot++]=j;
if(tot==3) break;
}
// cerr<<L<<" "<<R<<" "<<G[1][a[L].id]<<" "<<G[1][a[R].id]<<endl;
//A 1 B tt[0] C tt[1] D a[R].id E tt[2] F a[L].id
int tmp=min(abs(G[1][a[L].id]),abs(G[1][a[R].id]));
work(1,tt[0],tt[1],a[R].id,tt[2],tmp);
work(1,tt[0],tt[1],a[L].id,tt[2],-tmp);
if(G[1][a[L].id]==0) L++;
if(G[1][a[R].id]==0) R--;
}
// cerr<<"FUCK"<<endl;
}
}
void print() {
printf("%d\n",ans.size());
for(node i:ans) printf("%d %d %d %d %d\n",i.a,i.b,i.c,i.d,i.e);
}
void check() {
for(node i:ans) {
G1[i.a][i.b]+=i.e,G1[i.b][i.a]+=i.e,
G1[i.a][i.c]+=i.e,G1[i.c][i.a]+=i.e,
G1[i.a][i.d]+=i.e,G1[i.d][i.a]+=i.e,
G1[i.b][i.c]-=i.e,G1[i.c][i.b]-=i.e;
G1[i.b][i.d]-=i.e,G1[i.d][i.b]-=i.e;
G1[i.c][i.d]-=i.e,G1[i.d][i.c]-=i.e;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(G1[i][j]!=0) NoAnswer();
}
int main() {
int sum=0;
scanf("%d",&n);
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
scanf("%d",&G[i][j]);
for(int i=1;i<n;i++)
for(int j=i+1,x;j<=n;j++)
scanf("%d",&x),G[i][j]-=x,G[j][i]=G[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
sum+=G[i][j],G1[i][j]=G[i][j];
if(sum!=0) NoAnswer();
if(n==4) n4::solve();
else if(n==5) n5::solve();
else nn::solve();
check(),print();
}
Many LCS
Source: XXII Open Cup, GP of Southeastern Europe M
给定 \(K\),请构造两个 \(01\) 串 \(S,T\),使得他们本质不同 LCS 的个数恰好为 \(K\),且他们的长度 \(\le 8848\)。
\(1\le K\le 10^9\)。
Solution
考虑这样一种构造:
其 LCS 长度必定为 \(n+2m\),其中 \(m\) 个 \(1\),\(n+m\) 个 \(0\)。
从 \(S\) 中取出的每一个形如 \(0^{b_1}010^{b_2}01\ldots 0^{b_m}01\) 都对应着原串的一类 LCS,其中 \(\sum b_i=n\) 且 \(\forall i,0\le b_i\le a_i\)。
对于 \(b\) 序列计数可以容斥,但是太麻烦,不妨假设 \(a_i>\frac{n}{2}\),这样子的话只会存在一个 \(b_i\) 爆限制,那么可能的 LCS 数就会变成:
接下来考虑如何确定 \(m\),我们如果取 \(m=3\)——事实上对于每一个 \(x\),都可以用三个三角形数(\(\binom{n}{2}\))来表示——对于足够大的 \(K\),\(n\) 大概会变成 \(\sqrt{2K}\),所以我们考虑取 \(m=4\)。
有一个未经证实的猜想是每一个 \(x\) 都可以用至多五个四边形数(\(\binom{n}{3}\))来表示,而且这个猜想有人好像已经跑出了 \(10^{10}\) 内一定没问题,而且在 \(1\sim 10^9\) 内只有 \(241\) 个数需要五个数,如果遇到了这种不幸的情况,我们只需要给 \(n\) 加上 \(1\)。
那么,取 \(m=4\),找一个 \(n\) 出来,然后借助 \(01\) 背包,随便背出一个 \(a_i\),这题就解决了。
注意到有 \(a_i>\frac{n}{2}\) 的情况,如果 \(K\) 小 \(n\) 也会变小,所以需要特判。事实上,我们只需要对 \(m=2\) 构造,构造:
即可,至此,问题解决。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
#define int long long
int K,v[5];
bitset<3000005> f[5];
int C3(int n) {return 1ll*n*(n-1)*(n-2)/6;}
signed main() {
scanf("%lld",&K);
f[0]=1;
for(int i=1;i<=4;i++)
for(int j=1;j<=210;j++)
f[i]|=f[i-1]<<C3(j);
if(K<=4400) {
for(int i=1;i<=K+3;i++) putchar('0');
putchar('1');
for(int i=1;i<=K+3;i++) putchar('0');
putchar('1'),putchar('\n');
for(int i=1;i<=K+1;i++) printf("01");
putchar('\n');
}
else {
int n=1;
while(C3(n+3)<K) n++;
while(!f[4][C3(n+3)-K]) n++;
ll ret=C3(n+3)-K;
for(int i=4;i>=1;i--)
for(int j=1;j<=210;j++)
if(f[i-1][ret-C3(j)]) {
ret-=C3(j),v[i]=j;
break;
}
// cerr<<ret<<endl;
for(int i=1;i<=4;i++) {
for(int j=1;j<=n-v[i]+3;j++) putchar('0');
putchar('1');
}
putchar('\n');
for(int i=1;i<=n+4;i++) printf("01");
putchar('\n');
}
}
New Queries On Segment Deluxe
Source: XXII Open Cup, GP of Southeastern Europe B
给定一个大小为 \(k\times n\) 的矩阵 \(A\),根据其构造一个数列 \(B_j=\sum^k_{i=1}A_{i,j}\)。
维护下列操作:
1 t p l r x
:新建一个版本,这个版本是由版本 \(t\) 的 \(A_{p,i},i\in[l,r]\) 加上 \(x\) 得来的。2 t p l r y
:新建一个版本,这个版本是由版本 \(t\) 的 \(A_{p,i},i\in[l,r]\) 被覆盖为 \(y\) 得来的。3 t l r
:新建一个版本,这个版本是 copy 的版本 \(t\),求出 \(\min^r_{i=l}B_i\)。
\(1\le k\le 4\),\(1\le n\le 2.5\times 10^5\),\(1\le q\le 2\times 10^4\),\(|A_{i,j}|,|y|\le 10^8\),\(|x|\le 10^4\)。
Soltuion
观察:\(k\) 小哉,还有版本,不难考虑使用可持久化线段树。
可是需要维护什么呢?我们考虑维护大力 \(2^k\) 维护每一种取行的最小值,然后你突然发现这题好像做完了,剩下的就是常规线段树。
这题是不是很水?
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=250000,Q=20000;
const ll inf=1e18;
int K,n,q,cnt,rt[Q+10];
ll a[N+10][5];
struct node {
int ls,rs;
ll add[5],cov[5];
ll f[1<<4];
void init() {
for(int i=0;i<K;i++) add[i]=0,cov[i]=-inf;
for(int i=0;i<(1<<K);i++) f[i]=-inf;
ls=rs=0;
}
void print() {
cerr<<ls<<" "<<rs<<endl;
for(int i=0;i<K;i++) cerr<<add[i]<<" ";
cerr<<endl;
for(int i=0;i<K;i++) cerr<<cov[i]<<" ";
cerr<<endl;
for(int i=0;i<(1<<K);i++) cerr<<f[i]<<" ";
cerr<<endl;
}
} tr[N*4+Q*80+5];
void pushup(node a,node b, node &c) {
for(int i=0;i<(1<<K);i++)
c.f[i]=min(a.f[i],b.f[i]);
}
int newnode(int u) {
int v=++cnt;
tr[v].init();
tr[v].ls=tr[u].ls,tr[v].rs=tr[u].rs;
for(int i=0;i<K;i++)
tr[v].add[i]=tr[u].add[i],
tr[v].cov[i]=tr[u].cov[i];
for(int i=0;i<(1<<K);i++)
tr[v].f[i]=tr[u].f[i];
return v;
}
int build(int p,int l,int r) {
p=newnode(0);
if(l==r) {
for(int i=0;i<(1<<K);i++) {
tr[p].f[i]=0;
for(int j=0;j<K;j++) if(i&(1<<j))
tr[p].f[i]+=a[l][j];
}
return p;
}
int mid=(l+r)>>1;
tr[p].ls=build(tr[p].ls,l,mid);
tr[p].rs=build(tr[p].rs,mid+1,r);
pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
return p;
}
void add(node &p,int x,ll v) {
for(int i=0;i<(1<<K);i++)
if(i&(1<<x)) p.f[i]+=v;
p.add[x]+=v;
}
void cov(node &p,int x,ll v) {
for(int i=0;i<(1<<K);i++)
if(i&(1<<x)) p.f[i]=p.f[i^(1<<x)]+v;
p.cov[x]=v,p.add[x]=0;
}
void pushdown(node &p) {
p.ls=newnode(p.ls),p.rs=newnode(p.rs);
// printf("pushdown to %d %d\n",p.ls,p.rs);
for(int i=0;i<K;i++) {
if(p.cov[i]!=-inf)
cov(tr[p.ls],i,p.cov[i]),
cov(tr[p.rs],i,p.cov[i]),p.cov[i]=-inf;
if(p.add[i])
add(tr[p.ls],i,p.add[i]),
add(tr[p.rs],i,p.add[i]),p.add[i]=0;
}
}
void changeadd(int p,int l,int r,int x,int y,int h,ll v) {
if(x<=l&&r<=y) {
add(tr[p],h,v);
return;
}
int mid=(l+r)>>1;
pushdown(tr[p]);
if(x<=mid) changeadd(tr[p].ls,l,mid,x,y,h,v);
if(y>mid) changeadd(tr[p].rs,mid+1,r,x,y,h,v);
pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
}
void changecov(int p,int l,int r,int x,int y,int h,ll v) {
if(x<=l&&r<=y) {
cov(tr[p],h,v);
return;
}
int mid=(l+r)>>1;
pushdown(tr[p]);
if(x<=mid) changecov(tr[p].ls,l,mid,x,y,h,v);
if(y>mid) changecov(tr[p].rs,mid+1,r,x,y,h,v);
pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
}
ll query(int p,int l,int r,int x,int y) {
if(x<=l&&r<=y) return tr[p].f[(1<<K)-1];
int mid=(l+r)>>1;pushdown(tr[p]);
if(mid<x) return query(tr[p].rs,mid+1,r,x,y);
if(mid+1>y) return query(tr[p].ls,l,mid,x,y);
return min(query(tr[p].rs,mid+1,r,x,y),query(tr[p].ls,l,mid,x,y));
}
int main() {
scanf("%d %d %d",&K,&n,&q);
tr[0].init();
for(int i=0;i<K;i++)
for(int j=1;j<=n;j++)
scanf("%lld",&a[j][i]);
rt[0]=build(rt[0],1,n);
for(int i=1;i<=q;i++) {
int op,t,p,l,r;ll v;
scanf("%d",&op);
if(op==1) {
scanf("%d %d %d %d %lld",&t,&p,&l,&r,&v);
p--,rt[i]=newnode(rt[t]);
changeadd(rt[i],1,n,l,r,p,v);
}
if(op==2) {
scanf("%d %d %d %d %lld",&t,&p,&l,&r,&v);
p--,rt[i]=newnode(rt[t]);
changecov(rt[i],1,n,l,r,p,v);
}
if(op==3) {
scanf("%d %d %d",&t,&l,&r);
rt[i]=rt[t];
printf("%lld\n",query(rt[t],1,n,l,r));
}
}
}
LIS Counting
Source: XXII Open Cup, GP of Southeastern Europe D
给定 \(n,m,P\),对长度为 \(n\times m\) 的排列且其 LIS 长度为 \(n\),LDS 长度为 \(m\) 计数。
定义 \(f(pos,val)\) 表示强制第 \(pos\) 位为 \(val\) 的排列个数,输出所有的 \(f(pos,val)\) 对 \(P\) 取模的结果。
\(1\le n\times m\le 100\),\(10^8\le P\le 10^9\),且 \(P\) 一定为素数。
Solution
考虑没有强制怎么做,发现还是没有什么很好的做法。
考虑问题模型的转化,设 \(inc_i\) 表示以 \(i\) 结尾的 LIS 长度,\(dec_i\) 表示以 \(i\) 结尾的 LDS 长度,对于一个满足条件的排列 \(P\),构造 \(n\times m\) 的矩阵 \(A,B\),\(A\) 在 \((inc_i,dec_i)\) 处填 \(i\),\(B\) 在 \((inc_i,dec_i)\) 处填 \(P_i\)。
观察 1:\(A\) 的每行每列均单调上升。
证明:对于每一对 \(i<j\),有 \(inc_i<inc_j\) 或者 \(dec_i<dec_j\),那么必然不存在 \(A_{i,j}>A_{i,j+1}\) 或者 \(A_{i,j}>A_{i+1,j}\)。
观察 2:\(B\) 的每行单调下降,每列单调上升。
证明,考虑反证,首先一个事实是不存在多个相同的 \(B_{i,j}\),现在假设 \(B_{i,j}<B_{i,j+1}\),那么由 \(A_{i,j}<A_{i,j+1}\),可得 \(P_{A_{i,j}}<P_{A_{i,j+1}}\),所以 \(inc_{A_{i,j}}<inc_{A_{i,j+1}}\),与事实不符,所以 \(B_{i,j}>B_{i,j+1}\)。
单调上升的证明类似,这里不再赘述。
观察 3:\((A,B),P\) 一一对应。
证明:首先 \(P\to (A,B)\) 显然,考虑 \((A,B)\to P\),现在假设我们已经知道了 \((A,B)\),并推得了一个 \(P\),显然 \(P\) 的 LIS 长度至少为 \(n\),LDS 长度至少为 \(m\),因为每一行对应一个长度为 \(M\) 的子序列,每一列对应一个长度为 \(N\) 的子序列。
如果说存在一个大小为 \(n+1\) 的 LIS,考虑到 \(B\) 只有 \(n\) 行,那么必然有两个点在同一行,即,两个值 \(inc\) 相等,但是每一行需要递减,所以不可能。
这告诉我们,要想计数 \(P\),计数 \((A,B)\) 就可以了,而且 \(f(pos,val)\) 就恰恰对应了 \(f(A_{i,j},B_{i,j})\)。
考虑 DP,记录矩阵每一列已经填了多少个数,求方案数,从低到高枚举现在填那些数,转移是平凡的。
再另外求出某个数填在 \((i,j)\) 的方案数,那么 \(f(pos,val)=\sum_{i,j} g_A(pos,i,j)\times g_B(val,i,j)\)。
时间复杂度:\(O(Can\ Solve\ It)\)。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=100;
int n,m,mod,a[N+10],b[N+10],g[N+10][N+10][N+10];
bool FL;
unordered_map<ull,int> f[N+10];
ull encode(int *a) {
ull ret=0;
for(int i=1;i<=n;i++) ret=ret*(m+1)+a[i];
return ret;
}
void decode(ull x) {
for(int i=n;i;i--) a[i]=x%(m+1),x/=(m+1);
}
int main() {
scanf("%d %d %d",&n,&m,&mod);
if(n>m) FL=1,swap(n,m);
f[0][0]=1;
// cerr<<"FUCK0"<<endl;
for(int i=0;i<n*m;i++)
for(auto S:f[i]) {
decode(S.fi);
for(int j=1;j<=n;j++) if(a[j]!=m&&(j==1||a[j-1]>a[j])) {
a[j]++;
(f[i+1][encode(a)]+=S.se)%=mod;
a[j]--;
}
}
// cerr<<"FUCK1"<<endl;
for(int i=1;i<=n*m;i++)
for(auto S:f[i-1]) {
decode(S.fi);
for(int j=1;j<=n;j++) if(a[j]!=m&&(j==1||a[j-1]>a[j])) {
a[j]++;
for(int k=1;k<=n;k++) b[n-k+1]=m-a[k];
g[j][a[j]][i]=(g[j][a[j]][i]+1ll*S.se*f[n*m-i][encode(b)]%mod)%mod;
a[j]--;
}
}
// cerr<<"FUCK2"<<endl;
for(int pos=1;pos<=n*m;pos++) {
for(int j=1;j<=n*m;j++) {
int val=(FL?n*m-j+1:j),ans=0;
for(int a=1;a<=n;a++)
for(int b=1;b<=m;b++)
ans=(ans+1ll*g[a][b][pos]*g[a][m-b+1][val]%mod)%mod;
printf("%d ",ans);
}
puts("");
}
// cerr<<"FUCK3"<<endl;
}
Colourful Permutation Sorting
Source: XXII Open Cup, GP of Southeastern Europe I
给定一个排列 \(p\),及每一个位置的颜色,你可以进行如下的两个操作来使得排列被排序:
- 交换 \(p_i,p_j\),花费 \(S\)。
- 取出第 \(i\) 种颜色的全部元素并自由插回,花费 \(C_i\)。
\(1\le n\le 10^5\),颜色数 \(1\le k\le 5\),\(0\le S,C_i\le 10^9\)。
Solution
观察:一种颜色被操作至多 \(1\) 遍,两种操作相互独立。
意味着我们可以考虑先做操作 \(1\) 再做操作 \(2\)。
如果不使用操作二,那么贡献就是 \(S\times (n-\text{cyc})\)。
发现 \(k\) 小哉,直接暴力枚举哪些颜色被做了操作 \(2\),对于操作 \(2\) 我们其实等价于合并了若干点,使得原本的置换图变化形态。
为选定的颜色开虚点,根据原本的排列所对应的置换图,对于每一个置换环,将点所对应颜色的虚点按顺序连边,注意要额外处理没有选定颜色的置换环。
此时的贡献就是选定颜色的贡献加上 \(S\times (n-\text{cyc})\),\(\text{cyc}\) 是选出的环个数。
贡献最小化意味着想要在这个 \(k\) 个点的有向图求出尽可能多的环,接下来考虑这个问题。
观察:从小到大取走全部环最优。
首先,如果一个环经过一个自环,那么把自环分开最优。
其次,如果两个环交于一个二元环 \((i,j)\),设其是 \(x\to i\to j\to x\) 与 \(y\to j\to i\to y\),可以拆分成 \(x\to i \to y \to j\to x\) 与 \(i\to j\to i\),所以此时不会影响答案。
接下来考虑三元环 \((i,j,k)\),若存在三个环分别使用了 \((i,j),(j,k),(i,k)\) 这些边,那么他们的大小必然 \(\ge 3\),也就是说我这里会牵扯到 \(6\) 个点,可是只存在 \(5\) 个点,必然会有两个环交于同一点。
考虑这样一种构造:我们选了 \(i\to j\to y\to i\),\(i\to x\to k\to i\),\(j\to k\to z\to j\),构造 \(i\to j\to k\) 和 \(i\to x\to k\to z\to j\to y\to i\),最后一个环过大,必然存在两个相同的点,可以在这里拆开,那么环数不变。
接下来考虑四元环 \((i,j,k,l)\),其实类似于三元环的证明,同样可以得到贪心取最优。
暴力枚举加建模,然后暴力 DFS 找环,时间复杂度大概 \(O(2^k (k+n))\)?
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e5;
int n,K,S,cost[6],p[N+10],col[N+10],G[6][6],vis[N+10];
bool dfs(int u,int t,int k) {
if(!k) return u==t;
for(int i=0;i<K;i++) if(G[u][i]) {
G[u][i]--;
if(dfs(i,t,k-1)) return 1;
G[u][i]++;
}
return 0;
}
int main() {
int T;scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&K);
scanf("%d",&S);
for(int i=0;i<K;i++) scanf("%d",&cost[i]);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++) scanf("%d",&col[i]),col[i]--;
ll ret=1e18;
for(int st=0;st<(1<<K);st++) {
ll sum=0;int cyc=0;
for(int i=0;i<K;i++) if(st&(1<<i)) sum+=cost[i];
for(int i=0;i<K;i++)
for(int j=0;j<K;j++)
G[i][j]=0;
for(int i=1;i<=n;i++) vis[i]=0;
for(int i=1;i<=n;i++) if(!vis[i]&&(st&(1<<col[i]))) {
int j=p[i];vis[i]=1;
for(;!(st&(1<<col[j]));j=p[j]) vis[j]=1;
G[col[i]][col[j]]++;
}
for(int i=1;i<=n;i++) if(!vis[i]) {
cyc++;
for(int j=i;!vis[j];j=p[j]) vis[j]=1;
}
for(int i=1;i<=K;i++)
for(int j=0;j<K;j++)
while(dfs(j,j,i)) cyc++;
sum+=1ll*S*(n-cyc);
ret=min(ret,sum);
// cerr<<sum<<" "<<st<<" "<<cyc<<endl;
}
printf("%lld\n",ret);
}
}
Bruteforce
Source: XXII Open Cup, GP of IMO B
给定 \(k,w\) 和一个长度为 \(n\) 的序列 \(a\),定义这个序列的权值为:
- 将 \(a\) 序列排序后的数组称作 \(b\)。
- 计算 \(\sum_{i=1}^n \lfloor \frac{b_i\times i^k}{w}\rfloor\)。
动态修改,每次修改后求权值。
\(1\le n\le 10^5\),\(1\le k,w\le 5\),\(0\le a_i\le 10^5\)。
Solution
一个显然的事实是 \(\lfloor \frac{x}{w}\rfloor=\frac{x-x\bmod w}{w}\),考虑基于这个维护 \(\sum^n_{i=1} b_i\times i^k\) 与 \(\sum^n_{i=1} (b_i\times i^k)\bmod w\)。
\(b\) 要求是排序之后的,想到使用权值线段树来维护这坨东西。
接下来先考虑后面这个式子,考虑维护 \(cnt_{i,j}\) 来表示 \(b_c\mod w =i\) 和 \(c\mod w=j\) 的数字个数,插入时需要考虑当前子树的大小,合并时需要注意左子树的大小对右子树的影响。
考虑前面,对于每一个节点维护 \(f_i\) 表示现在的指数为 \(i\) 的式子值,插入与上述同理。考虑合并:首先左子树是直接加上,而右子树,则需要考虑左子树大小对右子树影响,也就是说要求出 \(\sum b_i\times (i+x)^k\),对于 \((i+x)^k\) 使用二项式定理拆开,变成 \(\sum b_i \times i^j\times x^{k-j}\times \binom{k}{j}\),\(\sum b_i\times i^j\) 已被处理,由此,可以在 \(O(k^2)\) 下完成合并。
总时间复杂度 \(O((w^2+k^2)(n+q)\log V)\)。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e5,mod=998244353;
int n,K,w,a[N+10],cnt[N*4+10][5][5],f[N*4+10][6],siz[N*4+10],g[6];
int C(int n,int m) {
if(m>n) return 0;
ll ret=1;
for(int i=n;i>=n-m+1;i--) ret=1ll*ret*i;
for(int i=1;i<=m;i++) ret/=i;
return ret%mod;
}
int fpow(int x,int y) {
int ret=1;
for(;y;y>>=1) {
if(y&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
}
return ret;
}
void pushup(int p) {
for(int i=0;i<w;i++) for(int j=0;j<w;j++)
cnt[p][i][j]=(cnt[p*2][i][j]+cnt[p*2+1][i][(j-siz[p*2]%w+w)%w])%mod;
for(int i=0;i<=K;i++) {
f[p][i]=f[p*2][i];
for(int j=0;j<=i;j++)
f[p][i]=(f[p][i]+(1ll*f[p*2+1][j]*C(i,j)%mod*fpow(siz[p*2],i-j)%mod))%mod;
}
siz[p]=siz[p*2]+siz[p*2+1];
}
void insert(int p,int l,int r,int v,int num) {
if(l==r) {
if(num>0) cnt[p][v%w][(siz[p]+1)%w]=(cnt[p][v%w][(siz[p]+1)%w]+num)%mod;
else cnt[p][v%w][siz[p]%w]=(cnt[p][v%w][siz[p]%w]+num)%mod;
if(num>0) for(int i=0;i<=K;i++) f[p][i]=(f[p][i]+1ll*v*fpow(siz[p]+num,i)%mod*num%mod+mod)%mod;
else for(int i=0;i<=K;i++) f[p][i]=(f[p][i]+1ll*v*fpow(siz[p],i)%mod*num%mod+mod)%mod;
siz[p]+=num;
return;
}
int mid=(l+r)>>1;
if(v<=mid) insert(p*2,l,mid,v,num);
else insert(p*2+1,mid+1,r,v,num);
pushup(p);
}
int main() {
scanf("%d %d %d",&n,&K,&w);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
insert(1,0,N,a[i],1);
// printf("%d\n",f[1][K]);
}
int q;scanf("%d",&q);
while(q--) {
int x,y;scanf("%d %d",&x,&y);
insert(1,0,N,a[x],-1),a[x]=y;
insert(1,0,N,y,1);
int ans=f[1][K];
// printf("%d\n",ans);
for(int i=1;i<w;i++)
for(int j=1;j<w;j++)
ans=(ans-1ll*cnt[1][i][j]*((i*fpow(j,K))%w)%mod+mod)%mod;
printf("%lld\n",1ll*ans*fpow(w,mod-2)%mod);
// cerr<<cnt[1][1][1]<<endl;
// printf("%d\n",ans);
}
}
K-onstruction
Source: XXII Open Cup, GP of IMO K
给定 \(K\),构造一个可重整数集 \(A\),满足如下条件:
- \(1\le |A|\le 30\);
- \(|A_i|\le 10^{16}\);
- 恰有 \(k\) 个 \(A\) 的子集满足子集和为 \(0\)。
\(1\le K\le 10^6\),有 \(10^3\) 组。
Solution
假设我们已经求出了一个集合 \(S\),使得恰有 \(x\) 个子集和为 \(0\),同时 \(S\) 内没有 \(0\),而且 \(S\) 的总和也不为 \(0\)。假设 \(P\) 是集合 \(S\) 中正数的总和,\(N\) 是集合 \(S\) 中负数的总和,不失一般性的,假设 \(P>-N\),考虑扩展这个集合。
现在我们往这个集合内加入一些 \(P\) 的非 \(0\) 倍数,设这些加入的数的集合是 \(T\),考虑 \(S\cup T\) 的子集为 \(0\) 的个数,这个值等于 \(x\times\) \(T\) 中子集和为 \(0\) 的个数加上在 \(T\) 中取 \(-P\) 的方案数(此时 \(S\) 内取 \(P\))。
不难发现 \(S\cup T\) 仍然满足 \(S\) 的条件,可以继续扩展。
容易发现上面所说的对应一个 DP,枚举 \(T\),假设其长度为 \(n\),同时求出子集和为 \(-P\) 的集合个数,那么我们是可以转移的,但是 \(T\) 太多了,不好转移。
考虑利用一些基本的元素来构造 \(T\),我们仅从 \(\{5,4,3,2,1,-1,-2,-3,-4,-5\}\) 这个集合的每一个子集出发,并且同时也使用这一些子集转移,这样子转移的复杂度就被拉下来了。
此时的转移对应一个最短路,记录方案即可。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
vector<int> h[210][210];
int A,B;
void get(vector<int> S) {
A=B=0;
for(int i=0;i<(1<<S.size());i++) {
int sum=0;
for(int j=0;j<S.size();j++)
if(i&(1<<j)) sum+=S[j];
// cerr<<i<<" "<<sum<<endl;
if(sum==0) A++;
if(sum==-1) B++;
}
// cerr<<A<<" "<<B<<endl;
}
void dfs(int dep,int las,int sum,vector<int> S) {
// cerr<<dep<<" "<<las<<" "<<sum<<endl;
if(sum>=0) {
get(S);
if(!h[A][B].size()) h[A][B]=S;
else if(S.size()<h[A][B].size()) h[A][B]=S;
}
else if(las<=0) return;
if(dep==10) return;
for(int i=las;i>=-5;i--) if(i) {
vector<int> T=S;T.pb(i);
dfs(dep+1,i,sum+i,T);
}
}
const int M=1e6;
vector<int> st[M+10];
int dis[M+10],pre[M+10],vis[M+10];
priority_queue<pii> pq;
void dij() {
for(int i=1;i<=M;i++) dis[i]=M;
dis[1]=1,pq.push({-dis[1],1});
while(pq.size()) {
int u=pq.top().se;pq.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=2;i<=56;i++)
for(int j=0;j<=64;j++) {
if(u*i+j>M) break;
if(h[i][j].size()&&(dis[u*i+j]>dis[u]+(int)h[i][j].size())) {
dis[u*i+j]=dis[u]+h[i][j].size();
pre[u*i+j]=u,st[u*i+j]=h[i][j];
if(!vis[u*i+j]) pq.push({-dis[u*i+j],u*i+j});
}
}
}
}
vector<ll> ans;ll tmp;
void calc(int x) {
if(x==1) {
ans.pb(1);tmp++;
return;
}
calc(pre[x]);ll o=0;
for(int i:st[x]) {
ans.pb(1ll*tmp*i);
if(i>0) o+=i;
}
tmp+=o*tmp;
}
void solve() {
int x;scanf("%d",&x);
ans.clear(),tmp=0,calc(x);
printf("%d\n",(int)ans.size());
for(ll i:ans) printf("%lld ",i);
puts("");
}
int main() {
// get({2,-2});
// system("pause");
dfs(1,5,0,{});dij();
int T;scanf("%d",&T);
while(T--) solve();
}
Deleting
Source: XXII Open Cup, GP of IMO D
给定一个数组 \([1,2,\ldots,n]\),你可以每次选择相邻的两个元素 \(i,j\) 并以 \(a_{i,j}\) 的代价将他们删除,同时将左右合并。
总代价是每一步代价的最大值,求出最小的代价。
\(2\le n\le 4000\),\(n\) 是个偶数,\(1\le A_{i,j}\le (\frac n 2)^2\)。
Solution
显然的有一个区间 DP,设 \(f_{l,r}\) 表示删除 \(l,r\) 所需要的代价最小是多少,转移显然,此处略去。
发现这竟然是 \(O(n^3)\) 的完全过不去,所以需要优化。
考虑二分一个答案 \(x\),接下来仅需要考虑 \(f_{l,r}\) 可否被删除。
枚举每一个状态 \(f\),从目前未被转移的某一段 \((l,r)\),考虑他能否被 \(f_{l+1,r-1}\) 转移过来,如果可以,考虑转移区间 DP 的分割区间部分,实际上将 \(r\) 看成中间点即可,这里就可以使用「世界上最好的数据结构(题解语)」——bitset 来优化。
综上,我们提出了一个 \(O(\frac{n^3}{w}\log n^2)\) 的做法,由于跑不满的原因可过。
这题有一个 \(O(\frac{n^3}{w})\) 的做法,但是:
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=4000;
int n,C[N+10][N+10];
bitset<N+10> f[N+10];
bool check(int x) {
for(int i=1;i<=n;i++) f[i].reset(),f[i][i-1]=1;
for(int l=n;l>=1;l--)
for(int r=l+1;r<=n;r+=2) {
if(f[l][r]) continue;
if(C[l][r]<=x&&f[l+1][r-1]) f[l][r]=1,f[l]|=f[r+1];
}
return f[1][n];
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j+=2)
scanf("%d",&C[i][j]);
int L=1,R=n*n/4;
while(L<=R) {
int mid=(L+R)>>1;
if(check(mid)) R=mid-1;
else L=mid+1;
}
printf("%d\n",R+1);
}
Cilantro
Source: XXII Open Cup, GP of Korea B
给定一个 \(01\) 入栈序列和一个 \(01\) 出栈序列,求出可以第一个出栈的元素并输出他们的编号和。
\(1\le n\le 5\times 10^6\)。
Solution
观察 1:对于任意一个 \(01\) 入栈序列,我们可以得到任意一个 \(01\) 出栈序列,只要他们 \(01\) 个数相同,但是我们并不知道编号。
观察 2:若 \(i<j\),且 \(j\) 可行,那么 \(i\) 也可行。
根据上面的观察 2,我们可以利用二分,二分出一个最大的 \(x\),使得 \(x\) 可行,时间复杂度 \(O(n\log n)\),大力卡常可以通过。
接下来,我们需要将这个做法优化到线性。
考虑从后往前扫描每一个点值,同时维护当前是否可以存在入栈出栈序列的对应关系。
考虑这样一个策略,先选一些点,把它们全部丢到后面。
如果此时存在这个对应关系,并且他们可以被选,我们就把当前 \(S\) 所对应的点丢到选的点集里面。
否则的话再来维护这个对应关系。
维护这个东西可以做到 \(O(n)\)。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=5e6;
int n,las,id=0;
char S[N+10],T[N+10];
int s[N+10],t[N+10];
ll ans=0;
int main() {
scanf("%d",&n);
scanf("%s%s",S+1,T+1);
for(int i=n;i>=1;i--)
if(las==0&&S[id+1]==T[i]) id++;
else {
las-=(S[i+id]=='Y'?1:-1);
las+=(T[i]=='Y'?1:-1);
}
for(int i=1;i<=id;i++) if(S[i]==T[1]) ans+=i;
printf("%lld\n",ans);
}
Flowerbed Redecoration
Source: XXII Open Cup, GP of Korea D
给定一个 \(n\times m\) 的字母矩形,现在你想要做一些操作:
- 首先,把一个 \(d\times d\) 的框子框在以 \((1,1)\) 为左上角的位置。
- 将框子对应的矩形顺时针整体旋转 \(90\) 度,然后将框子向右移动 \(x\) 格,如果不能移动,则将框子向下移动 \(y\) 格,并将框子横向移动到第一列。
- 重复上述操作直到不能再操作。
输出最后的字母矩形。
\(1\le n\times m\le 10^6\),不用担心会把框子移出去。
Solution
考虑置换形式。
首先依据物理知识,我们把移动框子转化成循环移动矩形。
也就是说,我们考虑 \(d\times d\) 框子内的矩形整体旋转,设这里可以形成的置换为 \(A\);向左循环位移 \(x\) 位形成的置换为 \(B\);向上循环位移 \(y\) 位形成的置换为 \(C\)。
那么如果将操作从向下移动 \(y\) 格处分割开,那么分割开的每一个部分为 \(H=(AB)^{k}A\),可以使用置换快速幂预处理。
那么整个操作也就是 \((HC)^{k}H\),注意这里的 \(k\) 与上述的 \(k\) 不是同一个定义。
利用置换快速幂可以做到 \(O(nm\log nm)\)。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e6;
int n,m,x,y,d;
vector<int> A,B,C,D,E,H,ans,c,rev;
char ch[N+10];
int bh(int x,int y) {return (x-1)*m+y;}
vector<int> merge(vector<int> a,vector<int> b) {
c.resize(n*m+1);
for(int i=1;i<=n*m;i++) c[i]=b[a[i]];
return c;
}
vector<int> ret;
vector<int> fpow(vector<int> x,int y) {
ret.resize(n*m+1);
for(int i=1;i<=n*m;i++) ret[i]=i;
for(;y;y>>=1) {
if(y&1) ret=merge(ret,x);
x=merge(x,x);
}
return ret;
}
int main() {
scanf("%d %d %d %d %d",&n,&m,&y,&x,&d);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>ch[bh(i,j)];
A.resize(n*m+1),B.resize(n*m+1),C.resize(n*m+1);
D.resize(n*m+1),E.resize(n*m+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i<=d&&j<=d) A[bh(i,j)]=bh(j,d-i+1);
else A[bh(i,j)]=bh(i,j);
int t1=(m-d)/x+1,t2=(n-d)/y+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
if(i<=d) B[bh(i,j)]=bh(i,(j-x-1+m)%m+1);
else B[bh(i,j)]=bh(i,j);
C[bh(i,j)]=bh((i-y-1+n)%n+1,j);
if(i<=d) D[bh(i,j)]=bh(i,(j+1ll*t1*x%m-1)%m+1);
else D[bh(i,j)]=bh(i,j);
E[bh(i,j)]=bh((i+1ll*t2*y%n-1)%n+1,j);
}
H=merge(fpow(merge(A,B),(m-d)/x+1),D);
ans=merge(fpow(merge(H,C),(n-d)/y+1),E);
rev.resize(n*m+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
rev[ans[bh(i,j)]]=bh(i,j);
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++)
printf("%c",ch[rev[bh(i,j)]]);
puts("");
}
}
Thanks to MikeMirzayanov
Source: XXI Open Cup, GP of Nizhny Novgorod F
CF1427D,加强版,\(n\le 20000\),\(q\le 120\)。
Solution
先改一波操作,每次只翻转区间内的元素,最后再来处理是否被需要再翻转一次。
考虑一个简单的问题——序列只有 \(0/1\) 的情况,不妨考虑先将相连的 \(0/1\) 缩起来,然后,假设我们现在是 \(0101010\cdots\),按奇偶性划分区间,\(0|10|1|01|0\cdots\),可以得到 \(00111000\cdots\) 的结果,会使段数减少大约 \(\frac 1 3\)。
所以对于序列只有 \(0/1\) 的情况,可以直接得到 \(\log_3n\) 的操作步数。
结合 \(0/1\) 的性质,按位考虑。
从高到低枚举每一位,对于每一位,先按照上述做法排序,对于连在一段的 \(01\),可以继续使用上述的做法分治下去。
注意到每一层可以并行,所以我们的操作步数是 \(\log_3n\log_2n\) 级别的。
Code
几乎是贺的,太难了。
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=20000;
int n,a[N+10],b[N+10],c[N+10];
void rev(vector<int> vi) {
reverse(vi.begin(),vi.end());
int L=0,R=n;
for(int x:vi) {
R-=x;
for(int j=0;j<x;j++) b[L+j]=a[R+j];
L+=x;
}
for(int i=0;i<n;i++) a[i]=b[i];
}
vector<int> vii;
vector<vector<int> > ans;
void solve(int L,int R,int dep) {
// cerr<<"solve "<<L<<" "<<R<<" "<<dep<<endl;
int tmp=-1;bool FL=1;
for(int i=L;i<R;i++) {
int now=a[i]>>dep&1;
if(now<tmp) FL=0;
tmp=now;
}
if(FL) {
vii.pb(R-L);
return;
}
vector<int> res;
res.clear();
int pre=-1,cnt=L;
for(int i=L;i<R;i++) {
int now=a[i]>>dep&1;
if(pre==-1) pre=now;
if(pre!=now) pre=now,res.pb(i-cnt),cnt=i;
}
res.pb(R-cnt);
for(int i=0;i<(int)res.size();i+=3) {
vii.pb(res[i]);
if(i+1==(int)res.size()) continue;
if(i+2==(int)res.size()) vii.pb(res[i+1]);
else vii.pb(res[i+1]+res[i+2]);
}
}
bool check(int dep) {
int pre=-1;
for(int i=0;i<n;i++) {
int ret=a[i]>>dep;
if(ret<pre) return 0;
pre=ret;
}
return 1;
}
int main() {
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]),a[i]--;
for(int i=20;i>=0;i--) {
if((1<<i)>=n) continue;
while(!check(i)) {
vii.clear();
int pre=-1,cnt=0;
for(int j=0;j<n;j++) c[j]=a[j]>>(i+1);
// for(int j=0;j<n;j++) printf("%d ",c[j]);puts("");
for(int j=0;j<n;j++) {
if(pre==-1) pre=c[j];
if(c[j]!=pre) {
// cerr<<cnt<<" "<<j<<endl;
solve(cnt,j,i),cnt=j,pre=c[j];
}
}
solve(cnt,n,i);
ans.pb(vii),rev(vii);
}
}
printf("%d\n",(int)ans.size());
for(auto v:ans) {
printf("%d ",(int)v.size());
for(int x:v) printf("%d ",x);
puts("");
}
}
Command and Conquer: Red Alert 2
Source: XXII Open Cup, GP of Xi'An H
给定一个三维空间,现在你在 \((-10^{100},-10^{100},-10^{100})\) 处,你每次可以使一维坐标 \(+1\),空间上有 \(n\) 个敌人,给出他们的坐标。
如果 \(\max(|x_s-x_e|,|y_s-y_e|,|z_s-z_e|)\le k\),则代表你可击杀这个敌人,敌人不会动,求出击杀所有敌人所需最小的 \(k\)。
多测,\(1\le n\le 5\times 10^5\),\(\sum n\le 2\times 10^6\)。
Solution
先将问题转化一下,空间结构上是以我所在的位置为中心打一个正方体,转化为以我所在的位置为左上角打一个正方体。
考虑现在能打到的敌人,如果我们的三维中的任意一维不是最小,我们贪心的移动到最小的位置,显然能打到的不会少。
考虑全部移动到了最小的维度,此刻我们必须开打,同样的,我们考虑贪心,打掉所需 \(k\) 最小的敌人。
上述东西可以使用 set 维护,\(O(n\log n)\)。
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ROF(i,a,b) for(int i=a;i>=b;i--)
const int N=5e5;
int n,ans=0,x[N+10],y[N+10],z[N+10];
set<pii> A,B,C;
void solve() {
scanf("%d",&n);
A.clear(),B.clear(),C.clear();
FOR(i,1,n) {
scanf("%d %d %d",&x[i],&y[i],&z[i]);
A.insert(mp(x[i],i)),B.insert(mp(y[i],i)),C.insert(mp(z[i],i));
}
ans=0;
while(A.size()) {
pii a=*A.begin(),b=*B.begin(),c=*C.begin();
int cx=a.fi,cy=b.fi,cz=c.fi;
vector<pii> tmp;
tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se}),a=b;
tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se}),a=c;
tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se});
sort(tmp.begin(),tmp.end());
ans=max(ans,tmp[0].fi);
A.erase(mp(x[tmp[0].se],tmp[0].se));
B.erase(mp(y[tmp[0].se],tmp[0].se));
C.erase(mp(z[tmp[0].se],tmp[0].se));
}
printf("%d\n",(ans+1)/2);
}
int main() {
int T;scanf("%d",&T);
FOR(i,1,T) solve();
}
Typing Contest
Source: XXII Open Cup, GP of Xi'An I
选拔 \(n\) 个人参加打字比赛,每个人两个属性 \(s_i,f_i\),一个人被加进团队内的贡献是 \(s_i\times (1-\sum_{j\in S,j\not=i}f_if_j)\)。
求出最大贡献。
\(1\le n\le 100\),\(\sum n\le 2000\),\(1\le s_i\le 10^{12}\),\(0\le f_i\le 1\)。
Solution
\(f_i\) 是浮点数,先乘上一个 \(100\) 来规避浮点数,那么单点贡献会变成 \(\frac 1 {10000} s_i(10000-\sum_{j\in S,j\not=i}f_if_j)\),下文忽略前方常数。
不难发现单点贡献可以变为 \(s_i(10000-f_i(\sum_{j\in S} f_j-f_i))\),如果固定 \(\sum_{j\in S} f_j\) 的话,可以快速得知贡献。
不妨枚举 \(\sum_{j\in S} f_j\),设其为 \(F\),那么单点贡献是 \(s_i(10000-f_i(F-f_i))\),问题变为典中典 \(01\) 背包。
发现复杂度是 \(O((\sum f_i)^2 n)\),不可过。
实际上,可以证明的是,\(F\) 的枚举最大值是 \(100\sqrt{n+2}\),这样复杂度稳过,证明如下:
设 \(t\) 是某一个位于 \(S\) 的集合,\(k\) 是 \(S\) 的大小。
考虑到有:
拆开得:
对所有值求和可以得到:
换个角度来看,对原式左右 \(\times f_t\):
对所有值求和可以得到:
移项可得:
联立 \(A,B\) 两式:
可以得到:
Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define pb push_back
#define fi first
#define se second
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ROF(i,a,b) for(int i=a;i>=b;i--)
const int N=100;
int n,f[N+10];
ll s[N+10],dp[1012],ans;
void solve() {
scanf("%d",&n);
FOR(i,1,n) {
db x;scanf("%lld %lf",&s[i],&x);
f[i]=(x*100+0.5);
}
ans=0;
FOR(v,1,1010) {
memset(dp,0,sizeof dp);
FOR(i,1,n) ROF(j,v,f[i])
dp[j]=max(dp[j],dp[j-f[i]]+s[i]*(10000-f[i]*(v-f[i])));
ans=max(ans,dp[v]);
}
printf("%.9lf\n",1.0*ans/10000);
}
int main() {
int t;scanf("%d",&t);
FOR(i,1,t) solve();
}