如果这就是人类脑海的话 雪白纸上划出血红层层痕迹 不如杀死这些记忆
test24
超级集合super
正序不太好考虑,考虑一下倒序怎么做。假设当前的位置为 \(x\),我们来考虑上一轮的位置是什么,设 \(c(y)\) 表示上一轮 \(<y\) 的 \(a_k\) 个数即编号 \(y\) 向本轮的减少量,应该有 \((x+c(v)=y) \in (a_{c(v)},a_{c(v)+1})\)。注意到 \(c(y)\) 单调不降,特判 \(a_1>1\),顺序枚举 \(i=c(v)\) 考要做 \(\min(res,\lfloor\frac{(a[i+1]-1-ans}{i}\rfloor)\) 次即可。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
using namespace std;
const int N=100005;
int id, T, n, k, a[N], ans;
void mian() {
cin >> n >> k, ans=1;
up(i,1,n) cin >> a[i];
if(a[1]!=1) { cout << 1 << '\n'; return; }
up(i,1,n-1) {
int t=min((a[i+1]-1-ans)/i,k);
k-=t, ans+=i*t;
}
ans+=k*n;
cout << ans << '\n';
}
signed main() {
// freopen("1.txt","r",stdin);
freopen("superset.in","r",stdin);
freopen("superset.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> id >> T;
while(T--) mian();
return 0;
}
宝可梦pokemon
这个永久增加看着很烦,但是发现假设存在一个雇佣子序列 \(a\to b\to c\) 满足 \(b/c\) 都是用 \(j\) 打败 \(a/b\) 的,那么直接让 \(c\) 打败 \(a\) 也是可以的,所以我们认为属性并非永久增加求出的答案是一样的。
考虑一下 \(y\) 用 \(j\) 打败 \(i\) 怎么转移。\(a_{y,j}>a_{x,j}\),费用 \(c_y\),\(a_{y,j}<a_{x,j}\),费用 \(c_y+a_{x,j}-a_{y,j}\)。
感觉是一个轮回形态的转移,普通 dp 比较难了,考虑简图跑最短路做 dp。先对于每一种属性值排序(大到小为例),然后转移有向前缀和向后缀两种,非常经典的建立两条辅助链,连接进入前缀贡献 \(a_{x,j}\) 和离开前缀贡献 \(c_y-a_{y,j}\),进入后缀贡献 \(0\) 离开后缀贡献 \(c_y\)。
自我欺骗这一块,不要有笨蛋觉得建了图边数点数对就可以随便最短路惹,不难发现上述东西跟暴力转移的区别极其有限,spfa 就是在这种网格图上跑的最慢的。我们希望钦定边权非负这样子可以跑复杂度绝对正确的 dij,希望提高 \(c_y-a_{y,j}\) 和降低 \(a_{x,j}\) 来达成这个目的,考虑 \(c-a\) 的最小值增加起来,对应的前缀直接减仍然非负,但是后缀怎么办,不妨考虑一段段递归这样子加,最后再段间补上 \(a-c\) 的非负差。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int N=600005;
int id, j, T, n, m, tot, c[N], dis[N], pos[N], tag[N], stk[N], top, val[N];
vector<int> a[N];
vector<pii> to[N];
priority_queue<pii> q;
inline void eadd(int u,int v,int w) {
to[u].pb(mp(v,w));
}
void mian() {
cin >> n >> m, tot=n;
up(i,1,n) cin >> c[i];
up(i,1,n) {
a[i].resize(m+1);
up(j,1,m) cin >> a[i][j];
}
up(i,1,n) pos[i]=i;
for(j=1; j<=m; ++j) {
sort(pos+1,pos+1+n,[](int A,int B){return a[A][j]<a[B][j];});
up(i,1,n) val[i]=a[pos[i]][j]-c[pos[i]];
stk[top=1]=n+1;
dn(i,n,1) {
while(top>1&&val[i]>val[stk[top]]) --top;
stk[++top]=i;
}
dn(g,top,2) {
int pre=stk[g+1], l=stk[g], r=stk[g-1]-1;
if(l>1) eadd(tot+1,tot,val[l]-val[pre]);
up(i,l,r) {
++tot;
int u=pos[i];
if(i>l) eadd(tot,tot-1,0);
eadd(u,tot,a[u][j]-val[l]);
eadd(tot,u,val[l]-val[i]);
}
}
dn(i,n,1) {
++tot;
int u=pos[i];
eadd(u,tot,0);
eadd(tot,u,c[u]);
if(i<n) eadd(tot,tot-1,0);
}
}
memset(dis,0x3f,sizeof(dis));
dis[1]=0, q.push(mp(0,1));
while(q.size()) {
int x=q.top().second; q.pop();
if(tag[x]) continue;
tag[x]=1;
for(pii p:to[x]) {
int y=p.first, w=p.second;
if(dis[x]+w<dis[y]) {
dis[y]=dis[x]+w;
q.push(mp(-dis[y],y));
}
}
}
cout << dis[n] << '\n';
up(i,1,tot) a[i].clear(), to[i].clear(), tag[i]=0;
}
signed main() {
freopen("pokemon.in","r",stdin);
freopen("pokemon.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> id >> T;
while(T--) mian();
return 0;
}
城市地铁
先考虑 \(lca(a,b)=b\) 的怎么做,显然是在每个点都尽可能往上跳即可。预处理每个点最上可以跳到哪里,可以排序+并查集来做,然后预处理一个倍增数组就能动态求了。
现在考虑 \(lca(a,b)=p\) 的怎么做,分两种情况,在 \(p\) 换乘地铁,把链拆开就能算了,或者找到一个经过 \(p\) (进一步说 \(lca=p\))的地铁 \((x,y)\),\(a\to (l=x,\dots,p),b\to (r=y,\dots,p)\),\(l\to r\),首先 \(l,r\) 一定可以选尽可能调到不必 \(p\) 浅的地方,像链一样处理贡献之后问题变成了对于 \((l,r)\) 是否存在 \((x,y)\) 满足 \(x/y\) 分别在 \(l/r\) 的子树里面,刻画子树里面就是 \(dfn\) 在某个区间,问题等价于二位数点,注意带着脑子写代码。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int N=400005, M=23;
int id, n, m, q, cw, T, L[N], R[N], stamp, fa[N][M], dep[N], tag[N];
int X[N], Y[N], Ans[N], cnt[N], dsu[N], lnk[N], g[N][M], tr[N];
pii wu[N], len[N];
vector<int> to[N];
vector<pii> Q[N];
void dfs(int x,int fad) {
L[x]=++stamp, dep[x]=dep[fad]+1, fa[x][0]=fad;
up(i,1,T) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int y:to[x]) if(y!=fad) dfs(y,x);
R[x]=stamp, dsu[x]=x;
}
inline int lca(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
dn(i,T,0) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
dn(i,T,0) if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i];
return fa[x][0];
}
inline void add(int x,int v) { for( ; x<=n; x+=x&-x) tr[x]+=v; }
inline int ask(int x) { int ret=0; for( ; x; x-=x&-x) ret+=tr[x]; return ret; }
int get(int x) { return x==dsu[x]?x:dsu[x]=get(dsu[x]); }
signed main() {
freopen("metro.in","r",stdin);
freopen("metro.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> id >> n, T=log2(n);
up(i,2,n) {
int u, v;
cin >> u >> v;
to[u].pb(v);
to[v].pb(u);
}
dfs(1,0);
cin >> m;
up(i,1,m) {
int x, y;
cin >> x >> y;
int p=lca(x,y);
wu[++cw]=mp(p,x);
wu[++cw]=mp(p,y);
if(L[y]<L[x]) swap(x,y);
len[i]=mp(x,y);
}
sort(wu+1,wu+1+cw,[](pii i,pii j){return dep[i.first]<dep[j.first];});
up(u,1,cw) {
for(int i=wu[u].second; ; ) {
if(!(i=get(i))) break;
if(dep[i]<dep[wu[u].first]) break;
lnk[i]=wu[u].first, dsu[i]=get(fa[i][0]);
}
}
up(i,1,n) g[i][0]=lnk[i]?lnk[i]:i;
up(j,1,T) up(i,1,n) g[i][j]=g[g[i][j-1]][j-1];
cin >> q;
up(i,1,q) {
int x, y;
cin >> x >> y;
if(x==y) { Ans[i]=0; continue; }
int p=lca(x,y);
if(p==x) swap(x,y);
if(p==y) {
dn(j,T,0) if(dep[g[x][j]]>dep[p]) x=g[x][j], Ans[i]+=(1<<j);
if(dep[g[x][0]]>dep[p]) Ans[i]=-1; else ++Ans[i];
continue;
}
dn(j,T,0) if(dep[g[x][j]]>dep[p]) x=g[x][j], Ans[i]+=(1<<j);
dn(j,T,0) if(dep[g[y][j]]>dep[p]) y=g[y][j], Ans[i]+=(1<<j);
if(dep[g[x][0]]>dep[p]||dep[g[y][0]]>dep[p]) Ans[i]=-1;
else {
tag[i]=1;
if(L[y]<L[x]) swap(x,y);
Q[L[x]-1].pb(mp(-i,y));
Q[R[x] ].pb(mp( i,y));
}
}
sort(len+1,len+1+m,[](pii i,pii j){return L[i.first]<L[j.first];});
int j=1;
up(i,1,n) {
while(j<=m&&L[len[j].first]<=i) {
add(L[len[j].second],1);
++j;
}
for(pii p:Q[i]) {
int id=p.first, x=p.second;
if(id>0) cnt[+id]+=ask(R[x])-ask(L[x]-1);
if(id<0) cnt[-id]-=ask(R[x])-ask(L[x]-1);
}
}
up(i,1,q) {
if(tag[i]) cout << Ans[i]+2-(cnt[i]>0) << '\n';
else cout << Ans[i] << '\n';
}
return 0;
}
寿司晚宴sushi
关心吃寿司的喜爱排名位置 \(\{C_i\},\{A_i\},\{B_i\}\),首先要求 \(\{a_{A_i}\},\{b_{B_i}\},\{c_{C_i}\}\) 是 \(\{1,\dots,n\}\) 的划分,然后要求 \(a_{A_i},b_{B_i}\) 在 \(c\) 中比 \(c_{C_i}\) 出现的晚,且 \(c_{C_{i}}\) 比 \(c_{C_{i+1}}\) 出现的早,对小A小B同理。这个限制像是经典问题诶,在 \(c\) 里面 \(a_{A_i’(\geq i)},b_{B_i'(\geq i)},c_{C_i'(>i)}\) 肯定都在 \(c_{C_i}\) 的后面就够了,考虑倒着插 \(c_{C_i}\) 肯定放在第一个位置,对应的 \(a_{A_i}/b_{B_i}\) 是随便找个后面的位置放就好了。也就是说对于一个合法的 \(\{c_{C_i}\}\),其对应着 \(\sum_{i=1}^{n/3} (3i-2)(3i-1)\) 个 \(c\),之后只用考虑 \(\{c_{C_i}\}\) 的方案数了。
吃寿司的有序序列分别为 \(\{a_{A_i}\},\{b_{B_i}\},\{c_{C_i}\}\),考虑确定 \(\{a_{A_i}\},\{b_{B_i}\}\) 的前提刻画一下 \(c\) 的条件和方案数量。
- \(\{a_{A_i}\},\{b_{B_i}\},\{c_{C_i}\}\) 是 \(\{1,\dots,n\}\) 的划分
- \(c_{C_i}\neq a_{j(\leq A_i)},c_{C_i}\neq b_{j (\leq B_i)}\),对小 A 小 B 同理。
- 进一步考虑怎么计算 \(c\) 的方案数,考虑 \(c_{C_i}\) 选什么,\(i\) 越大限制严格严格,从大到小填, \(a_{1\to A_i},b_{1\to B_i}\) 不能选,\(j>i,a_{A_j},b_{B_j},c_{C_j}\) 不能选,那么方案数就是 \(\sum_{i=1}^{n/3} (3i-cnt_{A_i,B_i})\),其中 \(cnt_{i,j}\) 表示 \(a_{1\to i},b_{1\to j}\) 的并集大小。
之后带着系数(\(c\) 的方案数)去 dp 合法的 \(\{a_{A_i}\},\{b_{B_i}\}\) 方案数即可。设 \(f_{t,i,j}\) 表示考虑到 \(t\) 轮,\(A_{t}=i,B_t=j\) 的带权方案数量,转移是容易的,要写一个前缀和优化。
#include<bits/stdc++.h>
#define ll long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int N=505, P=1e9+7;
int id, T, n, a[N], b[N], tag[N], sub[N][N], ca[N][N], cb[N][N], f[N][N][N];
void mian() {
memset(tag,0,sizeof(tag));
memset(sub,0,sizeof(sub));
memset(ca,0,sizeof(ca));
memset(cb,0,sizeof(cb));
memset(f,0,sizeof(f));
cin >> n;
up(i,1,n) cin >> a[i];
up(i,1,n) cin >> b[i];
up(i,1,n) {
tag[a[i]]=1, sub[i][0]=i;
up(j,1,n) sub[i][j]=sub[i][j-1]+(!tag[b[j]]);
}
up(i,1,n) up(j,1,i) ca[i][a[j]]=1, cb[i][b[j]]=1;
up(i,0,n) up(j,0,n) f[0][i][j]=1;
up(t,1,n/3) up(i,1,n) up(j,1,n) {
if(!cb[j][a[i]]&&!ca[i][b[j]]) f[t][i][j]=(ll)f[t-1][i-1][j-1]*(3*t-sub[i][j])%P;
f[t][i][j]=((((ll)f[t][i][j]+f[t][i-1][j])%P+f[t][i][j-1])%P-f[t][i-1][j-1])%P;
}
up(i,1,n/3) f[n/3][n][n]=(ll)f[n/3][n][n]*(3*i-2)%P*(3*i-1)%P;
cout << (f[n/3][n][n]+P)%P << '\n';
}
signed main() {
freopen("sushi.in","r",stdin);
freopen("sushi.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> id >> T;
while(T--) mian();
return 0;
}