vp + 补题 + 随机做题 记录三
按随机顺序排序
2024“钉耙编程”中国大学生算法设计超级联赛(1) 1004 传送
很厉害的线段树分治trick-在线并查集子树加。
转化下题意就是线段树分治,在时间线段树叶节点对1所在并查集整体加当前时间,肯定要采用懒标记的思想,考虑在并查集根打懒标记。
因为这里下放懒标记是困难的,就采用标记永久化的思想,每个点的真实值为到根的懒标记之和,懒标记不下放。
合并时,假设 \(x\) 合并到 \(y\) 上,求出两个并查集的根 \(fx,fy\),查询一下 \(fy\) 的真实值,给 \(fx\) 的懒标记扣去这个值,正确性显然。
撤销时,查一下现在时刻 \(fy\) 的真实值,给 \(fx\) 加上这个值。
因为按秩合并树高是 log 级别的,所以可以暴力做。
复杂度在这题是 2log 的,这题卡常,需要精细实现。
代码
#pragma GCC optimize(3)
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=6e5+5;
int n, m;
int s[N], e[N];
vector<int> vec[N<<2];
inline void ins(int p, int l, int r, int L, int R, int x){
if(L>R) return ;
if(L<=l&&r<=R) {
vec[p].emplace_back(x);
return ;
}
int mid=(l+r)>>1;
if(L<=mid) ins(p<<1, l, mid, L, R, x);
if(R>mid) ins(p<<1|1, mid+1, r, L, R, x);
}
int fa[N], sz[N]; ll tg[N];
inline int gf(int x){
while(x^fa[x]) x=fa[x];
return x;
}
pii stk[N]; int top;
inline ll sum(int x){
ll res=tg[x];
while(x^fa[x]) x=fa[x], res+=tg[x];
return res;
}
inline void merge(int x, int y){
x=gf(x); y=gf(y);
if(x==y) return ;
if(sz[x]>sz[y]) swap(x, y);
tg[x]-=sum(y);
stk[++top]=mapa(x, sz[y]);
fa[x]=y; sz[y]+=sz[x];
}
inline void back(){
int x=stk[top].fi, rsz=stk[top].se; --top;
int y=fa[x];
tg[x]+=sum(y);
sz[y]=rsz;
fa[x]=x;
}
inline void dfs(int p, int l, int r){
int lst=top;
for(auto t:vec[p]){
merge(s[t], e[t]);
}
if(l==r){
// for(int i=1; i<=n; ++i) cout<<tg[i]<<' ';
// cout<<endl;
int rt=gf(1);
tg[rt]+=l;
while(top>lst) back();
return ;
}
int mid=(l+r)>>1;
dfs(p<<1, l, mid); dfs(p<<1|1, mid+1, r);
while(top>lst) back();
}
int main(){
// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
read(n); read(m);
for(int i=1, l, r; i<=m; ++i){
read(s[i]); read(e[i]); read(l); read(r);
ins(1, 1, n, l, r, i);
}
for(int i=1; i<=n; ++i) fa[i]=i, sz[i]=1;
dfs(1, 1, n);
ll res=0;
for(int i=1; i<=n; ++i) res^=tg[i]/*, cout<<tg[i]<<endl*/;
cout<<res<<endl;
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(1) 1006 序列立方
因为 \(t^3=t+C_{t}^2\times 3\times C_{2}^1+C_{t}^3\times 6\)(观察力惊人),所以可以转化为求选三个完全相同的子序列的方案数,
记 \(f[i][j][k]\) 为选的三个子序列的结尾分别是 \(i,j,k\) 且 \(a_i=a_j=a_k\) 的方案数,转移时枚举三维下标均小于当前的状态,可以三维前缀和优化。
(这转化是怎么观察得到的……)
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=300, mod=998244353;
int n, a[N]; ll f[N][N][N], g[N][N][N];
inline void add(ll &x, ll y){x+=y; if(x>=mod) x-=mod;}
inline void del(ll &x, ll y){x-=y; if(x<0) x+=mod;}
int main(){
// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
read(n);
for(int i=1; i<=n; ++i) read(a[i]);
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
for(int k=1; k<=n; ++k){
if(a[i]==a[j]&&a[j]==a[k]) {
f[i][j][k]=1;
add(f[i][j][k], g[i-1][j-1][k-1]);
}
g[i][j][k]=f[i][j][k];
add(g[i][j][k], g[i-1][j][k]);
add(g[i][j][k], g[i][j-1][k]);
add(g[i][j][k], g[i][j][k-1]);
del(g[i][j][k], g[i-1][j-1][k]);
del(g[i][j][k], g[i-1][j][k-1]);
del(g[i][j][k], g[i][j-1][k-1]);
add(g[i][j][k], g[i-1][j-1][k-1]);
}
}
}
printf("%lld\n", g[n][n][n]);
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(1) 1010 众数
数据随机,所以答案大概率是前 \(t\) 大,问题变为求某个数字在给定区间内能成为最大值区间的子区间数。
考虑用堆去求,初始放置 \((l,r)\),每次求出堆顶代表区间的最大值的位置 \(p\),那么这个数至少有 \((p-l+1)\times (r-p+1)\) 的子区间数。
之后再把 \((l,p-1)\) 和 \((p+1, r)\) 丢进堆里,这样做 \(t\) 次就好。
这样做时间复杂度是 \(O(nlogn+mtlogn)\) 的。
Larunatrecy 提出了可以用笛卡尔树代替堆去掉一个log,复杂度变为 \(O(nlogn+mt)\),很厉害,回头实现一下。
代码
#pragma GCC optimize(3)
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e5+5;
int T, n, m;
int a[N];
int st[N][20], lg[N];
inline int cmp(int x, int y){
return a[x]<a[y]?y:x;
}
inline void init(){
for(int i=2; i<=n; ++i) lg[i]=lg[i>>1]+1;
for(int i=1; i<=n; ++i) read(a[i]), st[i][0]=i;
for(int t=1; t<=lg[n]; ++t){
for(int i=1; i+(1<<t)-1<=n; ++i){
st[i][t]=cmp(st[i][t-1], st[i+(1<<(t-1))][t-1]);
}
}
}
inline int get(int l, int r){
int t=lg[r-l+1];
return cmp(st[l][t], st[r-(1<<t)+1][t]);
}
struct node{
int l, r;
bool operator <(const node &t) const{
return a[get(l, r)]<a[get(t.l, t.r)];
}
};
int main(){
// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
read(T);
while(T--){
read(n); read(m);
init();
ll out=0;
for(int i=1; i<=m; ++i){
int l, r; read(l); read(r);
priority_queue<node> q;
unordered_map<int, ll> h;
int timer=0;
q.push((node){l, r});
while(!q.empty()&&timer<25){
++timer;
int cl=q.top().l, cr=q.top().r;
q.pop();
int p=get(cl, cr);
h[a[p]]+=(ll)(p-cl+1)*(cr-p+1);
if(cl<=p-1) q.push((node){cl, p-1});
if(p+1<=cr) q.push((node){p+1, cr});
}
ll mx=0; int ans=0;
for(auto t:h){
if(t.se>mx||(t.se==mx&&t.fi>ans)) mx=t.se, ans=t.fi;
}
// cout<<ans<<endl;
out^=(ll)i*ans;
}
printf("%lld\n", out);
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(1) 1011 众数
很恶心的分讨,至少我的实现是这样的。
二分答案是很容易想到的,验证的话可以考虑从反面出发。
设当前要验证 \(t\),枚举所有小于它的颜色,把这些点删掉后,在树上剩下的点连通块内形成的路径答案都一定小于 \(t\),求他们的并集就能得到所有答案小于 \(t\) 的路径,那么用 \(n^2\) 减去后就是答案大于等于 \(t\) 的路径。
每一种颜色对于路径数量都是最坏 \(O(n^2)\) 的,但题目限制保证了剩下的点在原树的dfs序上构成了 \(O(\sum_{x\in path(u,v)}{deg_x})\) 个连续区间,其中 \(path(u,v)\) 表示这个颜色能划分出的所有极大连续路径,那么可以转化为矩形并问题,某个颜色对于的矩形数量是 \(O(\sum_{x\in path(u,v)}{deg_x})\),加起来就是 \(O(n)\) 个矩形,所以可以在 \(O(nlogn)\) 的时间内完成一次验证。
那么加上外层的二分,总时间复杂度就是 \(O(nlog^2n)\),空间复杂度线性。
有非常多的细节,包括如果这个颜色不存在一个点满足其他所有点都在这个点的子树内,这种情况非常难讨论,还有其他很多corner case,总之就是知道理念就好,别碰。
我代码写了7k,但是在hdu上跑了暂时的最快rk2。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=7e4+5;
int T, n, m;
int a[N];
vector<int> e[N], bin[N];
int f[N], dep[N], dfn[N], out[N], timer;
bool ban[N];
inline void dfs(int x, int fa){
dep[x]=dep[fa]+1; f[x]=fa;
dfn[x]=++timer;
for(auto y:e[x]) if(y^fa) dfs(y, x);
out[x]=timer;
}
inline bool cmp(int x, int y){
if(dep[x]!=dep[y]) return dep[x]>dep[y];
return x<y;
}
vector<pii> add[N], del[N];
int sum[N<<2], cov[N<<2];
inline void push_up(int p, int l, int r){
if(l==r){
sum[p]=min(cov[p], 1);
return ;
}
if(cov[p]) sum[p]=r-l+1;
else sum[p]=sum[p<<1]+sum[p<<1|1];
}
inline void mdf(int p, int l, int r, int L, int R, int v){
if(L>R) return ;
if(L<=l&&r<=R) {
cov[p]+=v;
push_up(p, l, r);
return ;
}
int mid=(l+r)>>1;
if(L<=mid) mdf(p<<1, l, mid, L, R, v);
if(R>mid) mdf(p<<1|1, mid+1, r, L, R, v);
push_up(p, l, r);
}
inline void ins(int l1, int r1, int l2, int r2){
if(l1>r1||l2>r2) return ;
add[l1].emplace_back(l2, r2);
del[r1+1].emplace_back(l2, r2);
}
struct node{
int tp, u, v, l1, r1, l2, r2;
};
struct node2{
int l1, r1, l2, r2;
};
vector<node2> sp[N];
vector<node> pat[N];
inline ll check(int t){
for(int i=1; i<=n+1; ++i) add[i].clear(), del[i].clear();
for(int i=0; i<t; ++i) {
for(auto pt:sp[i]) ins(pt.l1, pt.r1, pt.l2, pt.r2);
for(auto pt:pat[i]){
int tp=pt.tp, u=pt.u, v=pt.v;
if(u==0){
for(auto y:e[tp]) if(y^f[tp]){
if(pt.l2<=pt.r2&&dfn[y]<=pt.l2&&pt.r2<=out[y]){
ins(dfn[y], pt.l2-1, dfn[y], pt.l2-1);
ins(dfn[y], pt.l2-1, pt.r2+1, out[y]);
ins(pt.r2+1, out[y], dfn[y], pt.l2-1);
ins(pt.r2+1, out[y], pt.r2+1, out[y]);
}
else if(pt.l1<=pt.r1&&dfn[y]<=pt.l1&&pt.r1<=out[y]){
ins(dfn[y], pt.l1-1, dfn[y], pt.l1-1);
ins(dfn[y], pt.l1-1, pt.r1+1, out[y]);
ins(pt.r1+1, out[y], dfn[y], pt.l1-1);
ins(pt.r1+1, out[y], pt.r1+1, out[y]);
}
else ins(dfn[y], out[y], dfn[y], out[y]);
}
continue;
}
int lst=0, cur=u;
while(cur^tp){
if(!lst) {
for(auto y:e[cur]) if(y^f[cur]){
if(pt.l1>pt.r1||!(dfn[y]<=pt.l1&&pt.r1<=out[y])) ins(dfn[y], out[y], dfn[y], out[y]);
else {
ins(dfn[y], pt.l1-1, dfn[y], pt.l1-1);
ins(dfn[y], pt.l1-1, pt.r1+1, out[y]);
ins(pt.r1+1, out[y], dfn[y], pt.l1-1);
ins(pt.r1+1, out[y], pt.r1+1, out[y]);
}
}
}
else{
for(auto y:e[cur]) if(y!=f[cur]&&y!=lst){
ins(dfn[y], out[y], dfn[y], out[y]);
}
}
lst=cur; cur=f[cur];
}
if(v==0){
for(auto y:e[tp]) if(y!=f[tp]&&y!=lst){
if(pt.l2<=pt.r2&&dfn[y]<=pt.l2&&pt.r2<=out[y]){
ins(dfn[y], pt.l2-1, dfn[y], pt.l2-1);
ins(dfn[y], pt.l2-1, pt.r2+1, out[y]);
ins(pt.r2+1, out[y], dfn[y], pt.l2-1);
ins(pt.r2+1, out[y], pt.r2+1, out[y]);
}
else ins(dfn[y], out[y], dfn[y], out[y]);
}
continue;
}
int lstx=lst;
lst=0, cur=v;
while(cur^tp){
if(!lst) {
for(auto y:e[cur]) if(y^f[cur]){
if(pt.l2>pt.r2||!(dfn[y]<=pt.l2&&pt.r2<=out[y])) ins(dfn[y], out[y], dfn[y], out[y]);
else {
ins(dfn[y], pt.l2-1, dfn[y], pt.l2-1);
ins(dfn[y], pt.l2-1, pt.r2+1, out[y]);
ins(pt.r2+1, out[y], dfn[y], pt.l2-1);
ins(pt.r2+1, out[y], pt.r2+1, out[y]);
}
}
}
else{
for(auto y:e[cur]) if(y!=f[cur]&&y!=lst){
ins(dfn[y], out[y], dfn[y], out[y]);
}
}
lst=cur; cur=f[cur];
}
for(auto y:e[tp]) if(y!=f[tp]&&y!=lst&&y!=lstx){
ins(dfn[y], out[y], dfn[y], out[y]);
}
}
}
for(int i=1; i<=n*4; ++i) sum[i]=cov[i]=0;
ll ret=(ll)n*n;
for(int i=1; i<=n; ++i) {
for(auto v:add[i]) mdf(1, 1, n, v.fi, v.se, 1);
for(auto v:del[i]) mdf(1, 1, n, v.fi, v.se, -1);
ret-=sum[1];
}
return ret;
}
int main(){
// freopen("D:\\nya\\acm\\A\\data.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
a[0]=-1;
while(T--){
timer=0;
read(n);
for(int i=0; i<=n; ++i) e[i].clear(), bin[i].clear(), ban[i]=0;
for(int i=1; i<=n; ++i) read(a[i]), bin[a[i]].emplace_back(i);
for(int i=1, x, y; i<n; ++i) read(x), read(y), e[x].emplace_back(y), e[y].emplace_back(x);
dfs(1, 0);
int mx=0;
while(mx<n&&bin[mx].size()) ++mx;
for(int i=0; i<mx; ++i){
sp[i].clear();
pat[i].clear();
sort(bin[i].begin(), bin[i].end(), cmp);
int p1=0, p2=0;
for(int j=0; j<(int)bin[i].size(); ++j){
int x=bin[i][j];
if(p1==0&&p2==0){
p1=x;
}
else if(p1!=0&&p2==0){
if(dfn[x]<=dfn[p1]&&out[p1]<=out[x]) p1=x;
else p2=x;
}
else {
if(dfn[x]<=dfn[p1]&&out[p1]<=out[x]) p1=x;
if(dfn[x]<=dfn[p2]&&out[p2]<=out[x]) p2=x;
}
}
if(p1==p2){
assert(p1==bin[i].back());
int tu=0, tv=0, l1=n+1, r1=0, l2=n+1, r2=0;
for(int j=0; j<(int)bin[i].size()-1; ++j){
int x=bin[i][j]; if(ban[x]) continue;
int lst=0;
do{
ban[x]=1; lst=x; x=f[x];
}while(a[x]==i);
if(lst==bin[i].back()) {
if(tu==0) tu=bin[i][j];
else tv=bin[i][j];
continue;
}
else if(lst==bin[i][j]) {
if(l1<=r1&&dfn[bin[i][j]]<=l1&&r1<=out[bin[i][j]]) pat[i].push_back((node){bin[i][j], 0, 0, l1, r1, n+1, 0});
else pat[i].push_back((node){bin[i][j], 0, 0, l2, r2, n+1, 0});
}
else {
if(l1<=r1&&dfn[bin[i][j]]<=l1&&r1<=out[bin[i][j]]) pat[i].push_back((node){lst, bin[i][j], 0, l1, r1, n+1, 0});
else pat[i].push_back((node){lst, bin[i][j], 0, l2, r2, n+1, 0});
}
if(dfn[lst]<=l1&&r1<=out[lst]) l1=dfn[lst], r1=out[lst];
else l2=dfn[lst], r2=out[lst];
}
if(!(dfn[tu]<=l1&&r1<=out[tu])) swap(l1, l2), swap(r1, r2);
pat[i].push_back((node){bin[i].back(), tu, tv, l1, r1, l2, r2});
sp[i].push_back((node2){1, dfn[bin[i].back()]-1, out[bin[i].back()]+1, n});
sp[i].push_back((node2){out[bin[i].back()]+1, n, 1, dfn[bin[i].back()]-1});
sp[i].push_back((node2){1, dfn[bin[i].back()]-1, 1, dfn[bin[i].back()]-1});
sp[i].push_back((node2){out[bin[i].back()]+1, n, out[bin[i].back()]+1, n});
}
else {
int l1=n+1, r1=0, l2=n+1, r2=0;
for(int j=0; j<(int)bin[i].size(); ++j){
int x=bin[i][j]; if(ban[x]) continue;
int lst=0;
do{
ban[x]=1; lst=x; x=f[x];
}while(a[x]==i);
if(lst==bin[i][j]) {
if(l1<=r1&&dfn[bin[i][j]]<=l1&&r1<=out[bin[i][j]]) pat[i].push_back((node){bin[i][j], 0, 0, l1, r1, n+1, 0});
else pat[i].push_back((node){bin[i][j], 0, 0, l2, r2, n+1, 0});
}
else {
if(l1<=r1&&dfn[bin[i][j]]<=l1&&r1<=out[bin[i][j]]) pat[i].push_back((node){lst, bin[i][j], 0, l1, r1, n+1, 0});
else pat[i].push_back((node){lst, bin[i][j], 0, l2, r2, n+1, 0});
}
if(dfn[lst]<=l1&&r1<=out[lst]) l1=dfn[lst], r1=out[lst];
else l2=dfn[lst], r2=out[lst];
}
if(dfn[p1]>dfn[p2]) swap(p1, p2);
int c[3], d[3];
c[0]=1, d[0]=dfn[p1]-1;
c[1]=out[p1]+1, d[1]=dfn[p2]-1;
c[2]=out[p2]+1, d[2]=n;
for(int _=0; _<3; ++_) for(int __=0; __<3; ++__) sp[i].push_back((node2){c[_], d[_], c[__], d[__]});
}
}
int l=0, r=mx, res=0; ll cnt=0;
while(l<=r){
int mid=(l+r)>>1;
ll cur=check(mid);
if(cur) {
res=mid; cnt=cur; l=mid+1;
}
else r=mid-1;
}
printf("%d %lld\n", res, cnt);
}
return 0;
}
[ONTAK2015] Bajtocja
考虑开 \(d\) 个并查集直接维护。
考虑判断两个点是否在 \(d+1\) 张图都联通,一种思路是把每张图每个联通块确定一个根,那么一个点在 \(d+1\) 张图所在联通块的根组成了一个长度为 \(d+1\) 的字符串,用字符串hash+桶查询即可维护。
采用按秩合并,这样能够更新每个点所属的联通块的根,总复杂度是 \(O(n+dmlogn+klogn)\) 的。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
typedef __int128 lint;
const int N=5e3+10;
const int bs1=1333337, bs2=1333331, mod1=998244353, mod2=1e9+7;
ll pw1[205], pw2[205];
int T, n, m, d, q;
ll c1[N], c2[N];
map<pair<ll, ll>, int> h;
ll ans;
struct Dsu{
int id;
int fa[N];
vector<int> vec[N];
inline int gf(int x){
if(x==fa[x]) return x;
return gf(fa[x]);
}
inline void merge(int x, int y){
x=gf(x); y=gf(y);
if(x==y) return ;
if(vec[x].size()<vec[y].size()) swap(x, y);
for(auto t:vec[y]){
// ans-=h[mapa(c1[t], c2[t])]-1;
ans-=2*h[mapa(c1[t], c2[t])]-1;
h[mapa(c1[t], c2[t])]--;
if(h[mapa(c1[t], c2[t])]==0) h.erase(h.find(mapa(c1[t], c2[t])));
c1[t]=(c1[t]-(ll)y*pw1[id]%mod1+mod1)%mod1;
c2[t]=(c2[t]-(ll)y*pw2[id]%mod2+mod2)%mod2;
vec[x].emplace_back(t);
c1[t]=(c1[t]+(ll)x*pw1[id]%mod1)%mod1;
c2[t]=(c2[t]+(ll)x*pw2[id]%mod2)%mod2;
++h[mapa(c1[t], c2[t])];
// ans+=h[mapa(c1[t], c2[t])]-1;
ans+=2*h[mapa(c1[t], c2[t])]-1;
}
vec[y].clear(); fa[y]=x;
}
inline void rebuild(int x){
for(int i=1; i<=x; ++i){
fa[i]=i; vec[i].clear(); vec[i].emplace_back(i);
c1[i]=(c1[i]+(ll)i*pw1[id]%mod1)%mod1;
c2[i]=(c2[i]+(ll)i*pw2[id]%mod2)%mod2;
}
}
}ds[205];
int main(){
//freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
//freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
pw1[0]=1; for(int i=1; i<=200; ++i) pw1[i]=pw1[i-1]*bs1%mod1;
pw2[0]=1; for(int i=1; i<=200; ++i) pw2[i]=pw2[i-1]*bs2%mod2;
// read(T);
T=1;
for(int i=0; i<=200; ++i) ds[i].id=i;
while(T--){
h.clear();
for(int i=1; i<=n; ++i) c1[i]=c2[i]=0;
// read(n); read(m); read(d); read(q);
read(d); read(n); read(q); m=0;
for(int i=0; i<=d-1; ++i) ds[i].rebuild(n);
for(int i=1; i<=n; ++i) h[mapa(c1[i], c2[i])]++;
// ans=0;
ans=n;
for(int i=1, x, y; i<=m; ++i){
read(x); read(y);
for(int j=0; j<=d; ++j) ds[j].merge(x, y);
}
int x, y, z;
while(q--){
read(x); read(y); read(z);
ds[z-1].merge(x, y);
printf("%lld\n", ans);
}
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(3) 1003 旅行
细节很多的轻工业DS。
这东西长得很像流,但是子树包含关系不好建图,模拟流大概就寄了。
按套路路径在lca处拆开成两条独立的返祖路径,这样可以用子树的DP维护。
考虑一种暴力的DP,用 \(f_{x,i}\) 表示在 \(x\) 的子树内存在一条未匹配的起点颜色为 \(i\) 的答案,特别的,令 \(f_{x,0}\) 表示子树内匹配完毕的答案。
合并时需要考虑根节点是否被用过,所以需要把 \(f_{x,0}\) 再细分,分成 \(f_{x,-1}\) 和 \(f_{x,0}\) 分别表示根用过/没用过的匹配完毕的答案。
这个东西每个子树只需要开最大子树大小的桶,看上去可以线段树合并维护,但是我不会。
考虑暴力一点的东西,把树重剖后重链上桶继承,这样每个点到根会经过 \(O(\log n)\) 次轻重链切换,合并次数就是 \(O(n\log n)\)。
合并两个桶只需要线性扫一遍新桶即可,用哈希 (std::unordered_map) 即可做到,所以时间复杂度是 \(O(n\log n)\),空间复杂度 \(O(n)\)。
非常强数据,使我细节调三天。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e5+5;
const ll inf=1000000000000000ll;
int T, n;
vector<int> e[N];
int c[N], w[N];
unordered_map<int, ll> h[N];
int id[N], sz[N];
ll tag[N];
inline int merge(int x, int y){
ll f0=h[x][0];
ll add=max(h[y][-1], h[y][0])+tag[y];
h[x][-1]+=add; h[x][0]+=add;
for(auto t:h[y]){
if(t.fi==-1||t.fi==0){
continue;
}
if(h[x].find(t.fi)==h[x].end()){
h[x][t.fi]=f0+tag[x]+t.se+tag[y]-tag[x];
}
else{
h[x][-1]=max(h[x][-1]+tag[x], h[x][t.fi]+tag[x]+t.se+tag[y])-tag[x];
h[x][t.fi]=max(h[x][t.fi]+tag[x]+add, f0+tag[x]+t.se+tag[y])-tag[x];
}
}
for(auto t:h[y]) h[x][t.fi]-=add;
tag[x]+=add;
tag[y]=0; h[y].clear();
return x;
}
inline void dfs(int x, int fa){
id[x]=x; sz[x]=1;
h[x].clear(); h[x][-1]=-inf; h[x][0]=0; h[x][c[x]]=w[x];
tag[x]=0;
vector<pii> vec;
for(auto y:e[x]){
if(y==fa) continue;
dfs(y, x);
sz[x]+=sz[y]; vec.emplace_back(sz[y], y);
}
if(sz[x]==1) return;
sort(vec.begin(), vec.end());
int mxs=vec.back().se;
id[x]=id[mxs];
if(h[id[x]].find(c[x])!=h[id[x]].end()){
ll pre=h[id[x]][c[x]];
h[id[x]][c[x]]=max(h[id[x]][c[x]], max(h[id[x]][0], h[id[x]][-1])+w[x]);
h[id[x]][0]=max(h[id[x]][0], h[id[x]][-1]);
h[id[x]][-1]=max(h[id[x]][-1], pre+w[x]);
}
else{
h[id[x]][c[x]]=max(h[id[x]][0], h[id[x]][-1])+w[x];
h[id[x]][0]=max(h[id[x]][0], h[id[x]][-1]);
h[id[x]][-1]=-inf;
}
for(auto t:vec) if(t.se!=mxs) id[x]=merge(id[x], id[t.se]);
}
int main(){
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
read(T);
while(T--){
read(n);
for(int i=1; i<=n; ++i) e[i].clear();
for(int i=1; i<=n; ++i) read(c[i]);
for(int i=1; i<=n; ++i) read(w[i]);
for(int i=1, x, y; i<n; ++i){
read(x); read(y);
e[x].emplace_back(y); e[y].emplace_back(x);
}
dfs(1, 0);
printf("%lld\n", max(h[id[1]][0], h[id[1]][-1])+tag[id[1]]);
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(7) 1001 纠缠点对

致敬传奇题目7505之不会1log调了一天2log卡常能过。
场上觉得这不是shaber结论+bitset嗯艹,然后mle+tle遗憾离场。
很明显有结论,两路径相交必然是lca较高的路径包含较低的路径的lca,我场上用这个结论写了个树上差分+bitset的逆天东西,但是起码是 \(O(\frac{n^2}{w}+nlogn)\) 的,比 \(O(\frac{n^2logn}{w})\) 强。
题解说能做1log,不会,写一下自己的2log做法。
在第一棵树上还是要做树上差分,考虑一个暴力的做法是对每个点维护到根的路径上所有点的信息,题解的第一种情况是在第二棵树上单点修改路径查询,第二种是路径修改和单点查询。
众所周知,路径是不好搞到1log的,所以默认路径是2log复杂度。
对于第一种,修改时在第一棵树上树剖,查询时四个点树上差分;第二种,修改时单点修改,查询时树剖出log个连续段。
这样问题就转化为了第一种情况是平面上平面修改长条查询,第二种是长条修改平面查询。
这就需要一种能做到任何操作复杂度均为 \(O(\log 第一维长度\log n)\) 的树套树,很遗憾,我不会这样的东西,用常见的树套树(bit套动态开点)做是3log的,寄。
这时候我突然想:我为啥非要在线做,这不是轻松离线?

也就是我把所有修改都挂在lca处,在第一棵树上全挂上去后做从根到叶子的线段树合并,查询时候四个点树上差分得到路径信息就行了,离线的正确性显然。
对………对吗?如果不考虑重复的话肯定是对的,但是对于lca相同但本质不同的路径来说会产生重复。
下面用 \(c_i\) 表示路径 \(i\) 在第一棵树上lca的深度,\(d_i\) 是第二棵树,我们考虑一下到底产生了哪些重复:
对于某个路径 \(x\),我们需要统计到的是 \(c_x\leq c_y\) 且在第二棵树上相交的路径,因为相等是在 \(c_x=c_y\) 时产生的,所以第一遍我们就做 \(c_x< c_y\) 的贡献,并且我们只能一遍处理出来 \(d_x\leq d_y\) 的,还需要第二遍做 \(d_x>d_y\) 的贡献。
接下来考虑 \(c_x=c_y\),需要算 \(d_x<d_y,d_x=d_y,d_x>d_y\) 三种情况,那么我们逆向思维,把两棵树交换,做 \(d_x\leq d_y,c_x=c_y\) 的贡献。
但是 \(c_x=c_y,d_x=d_y\) 会重复,所以第三遍只做 \(d_x<d_y, c_x=c_y\),最后再做一遍 \(c_x=c_y,d_x=d_y\) 就可以了。
复杂度因为含有路径操作,需要树剖,所以是2log。
然后就是最史的代码环节了,我一开始分讨写错了,一直以为是带push_down的线段树写挂的问题,在 Larunatrecy 的建议下改成了标记永久化,让线段树合并好写的同时减少了常数。
1log不会。
upd
会1log了,忘了路径加单点查和单点加路径查是可以通过子树加树上差分查和树上差分加子树查做到单log。
代码
#pragma GCC optimize(3)
#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 N=1e5+5;
int T, n, m;
struct TREE{
vector<int> e[N];
int dep[N], f[N], sz[N], son[N], tp[N], dfn[N], out[N], timer;
inline void dfs(int x, int fa){
sz[x]=1; f[x]=fa; son[x]=0; dep[x]=dep[fa]+1;
for(auto y:e[x]) if(y!=fa) {
dfs(y, x); sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
inline void dfs2(int x, int top){
tp[x]=top; dfn[x]=++timer;
if(son[x]) dfs2(son[x], top);
for(auto y:e[x]) if((y!=f[x])&&(y!=son[x])) dfs2(y, y);
out[x]=timer;
}
inline void init(){
for(int i=1; i<=n; ++i) e[i].clear();
for(int i=1, x, y; i<n; ++i){
fin>>x>>y; e[x].emplace_back(y); e[y].emplace_back(x);
}
timer=0;
dfs(1, 0); dfs2(1, 1);
}
inline int lca(int x, int y){
while(tp[x]!=tp[y]){
if(dep[tp[x]]<dep[tp[y]]) swap(x, y);
x=f[tp[x]];
}
return dep[x]<dep[y]?x:y;
}
}t1, t2;
int rt[N], idx;
ll sum[N*200], tag[N*200]; int ls[N*200], rs[N*200];
inline int gen(){
++idx; sum[idx]=tag[idx]=ls[idx]=rs[idx]=0;
return idx;
}
inline void init(){
idx=0;
for(int i=1; i<=n; ++i) rt[i]=0;
}
inline void mdf(int &p, int l, int r, int L, int R, int v){
if(!p) p=gen();
if(L<=l&&r<=R) {
tag[p]+=v; return ;
}
int mid=(l+r)>>1;
sum[p]+=v*(min(r, R)-max(l, L)+1);
if(L<=mid) mdf(ls[p], l, mid, L, R, v);
if(R>mid) mdf(rs[p], mid+1, r, L, R, v);
}
inline ll get(int p, int l, int r, int L, int R){
if(!p) return 0;
if(L<=l&&r<=R) return sum[p]+tag[p]*(r-l+1);
int mid=(l+r)>>1;
ll ret=tag[p]*(min(r, R)-max(l, L)+1);
if(L<=mid) ret+=get(ls[p], l, mid, L, R);
if(R>mid) ret+=get(rs[p], mid+1, r, L, R);
return ret;
}
inline int merge(int p, int q, int l, int r){
if(!q) return p;
if(!p) return q;
sum[p]+=sum[q]; tag[p]+=tag[q];
if(l==r){
return p;
}
int mid=(l+r)>>1;
ls[p]=merge(ls[p], ls[q], l, mid);
rs[p]=merge(rs[p], rs[q], mid+1, r);
return p;
}
inline void work(int x){
for(auto y:t1.e[x]) if(y^t1.f[x]) rt[y]=merge(rt[y], rt[x], 1, n), work(y);
}
inline void work2(int x){
for(auto y:t2.e[x]) if(y^t2.f[x]) rt[y]=merge(rt[y], rt[x], 1, n), work2(y);
}
struct node{
int x, y, lca, t;
}q[N];
int cnt[N];
vector<int> bin[N];
int main(){
// freopen("D:\\nya\\acm\\C\\test2.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
fin>>T;
while(T--){
ll ans=0;
fin>>n>>m;
t1.init(); t2.init();
for(int i=1; i<=m; ++i){
fin>>q[i].x>>q[i].y; q[i].lca=t1.lca(q[i].x, q[i].y); q[i].t=t2.lca(q[i].x, q[i].y);
}
init();
for(int i=1; i<=m; ++i){
mdf(rt[q[i].lca], 1, n, t2.dfn[q[i].t], t2.out[q[i].t], 1);
}
work(1);
for(int i=1; i<=m; ++i){
int x=q[i].x, y=q[i].y;
ans+=get(rt[q[i].x], 1, n, t2.dfn[x], t2.dfn[x]);
ans+=get(rt[q[i].y], 1, n, t2.dfn[x], t2.dfn[x]);
ans-=get(rt[q[i].lca], 1, n, t2.dfn[x], t2.dfn[x])*2ll;
ans+=get(rt[q[i].x], 1, n, t2.dfn[y], t2.dfn[y]);
ans+=get(rt[q[i].y], 1, n, t2.dfn[y], t2.dfn[y]);
ans-=get(rt[q[i].lca], 1, n, t2.dfn[y], t2.dfn[y])*2ll;
ans-=get(rt[q[i].x], 1, n, t2.dfn[q[i].t], t2.dfn[q[i].t]);
ans-=get(rt[q[i].y], 1, n, t2.dfn[q[i].t], t2.dfn[q[i].t]);
ans+=get(rt[q[i].lca], 1, n, t2.dfn[q[i].t], t2.dfn[q[i].t])*2ll;
ans-=get(rt[q[i].x], 1, n, t2.dfn[t2.f[q[i].t]], t2.dfn[t2.f[q[i].t]]);
ans-=get(rt[q[i].y], 1, n, t2.dfn[t2.f[q[i].t]], t2.dfn[t2.f[q[i].t]]);
ans+=get(rt[q[i].lca], 1, n, t2.dfn[t2.f[q[i].t]], t2.dfn[t2.f[q[i].t]])*2ll;
}
// cout<<ans<<endl;
init();
for(int i=1; i<=m; ++i){
mdf(rt[q[i].lca], 1, n, t2.dfn[q[i].x], t2.dfn[q[i].x], 1);
mdf(rt[q[i].lca], 1, n, t2.dfn[q[i].y], t2.dfn[q[i].y], 1);
mdf(rt[q[i].lca], 1, n, t2.dfn[q[i].t], t2.dfn[q[i].t], -2);
}
work(1);
for(int i=1; i<=m; ++i){
ans+=get(rt[q[i].x], 1, n, t2.dfn[q[i].t], t2.out[q[i].t]);
ans+=get(rt[q[i].y], 1, n, t2.dfn[q[i].t], t2.out[q[i].t]);
ans-=get(rt[q[i].lca], 1, n, t2.dfn[q[i].t], t2.out[q[i].t])*2ll;
}
// cout<<ans<<endl;
init();
for(int i=1; i<=m; ++i){
mdf(rt[q[i].t], 1, n, t1.dfn[q[i].lca], t1.dfn[q[i].lca], 1);
}
work2(1);
for(int i=1; i<=m; ++i){
ans+=get(rt[q[i].x], 1, n, t1.dfn[q[i].lca], t1.dfn[q[i].lca]);
ans+=get(rt[q[i].y], 1, n, t1.dfn[q[i].lca], t1.dfn[q[i].lca]);
ans-=get(rt[q[i].t], 1, n, t1.dfn[q[i].lca], t1.dfn[q[i].lca])*2ll;
}
// cout<<ans<<endl;
for(int i=1; i<=m; ++i){
bin[q[i].lca].emplace_back(i);
}
for(int i=1; i<=n; ++i) if(!bin[i].empty()){
for(auto j:bin[i]) ans+=cnt[q[j].t], ++cnt[q[j].t];
for(auto j:bin[i]) cnt[q[j].t]=0;
bin[i].clear();
}
fout<<ans;
fout.pc('\n');
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(7) 1002 生产机器
考虑计算有多少序列是合法的,合法定义为能被这些机器表示出来。
一个显然的贪心是从前到后枚举序列的每个元素,看当前机器剩余的元素数量是否足够产生这个元素,否则切换到下一个机器。
我们把每个元素来源的机器生成一个新序列,显然这是个双射,所以只需要计算这个新序列的种类即可。
因为要求用下一个机器的颜色 \(i\) 需要把当前机器的颜色 \(i\) 用完,所以定义 \(f_{i, j}\) 表示现在新序列轮到第 \(i\) 个机器去填,且钦定第一个元素颜色是 \(j\) 的方案数。
根据 \(\sum_{i=0}^{y}C_{x+i}^{i}=C_{x+y+1}^y\),可以得到转移方程。
然后再考虑新序列在当前机器截止的方案,根据 \(\sum_{i=0}^x\sum_{j=0}^yC_{i+j}^i=C_{x+y+2}^{x+1}-1\) 就能算出来。
需要特别考虑 \(a,b\) 其中一个为0的情况,因为上述 \(f_{i,j}\) 需要机器至少产生1个 \(j\) 颜色元素时才有定义,但为了良定义 \(f\),我们不管 \(a,b\) 是否为0直接做,推一推式子就能发现是对的。
复杂度是 \(O(n+|V|)\) 。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e6+100, mod=1e9+7;
int T, n;
ll frc[N], inv[N];
inline ll C(int x, int y){
if(x<0||y<0||x-y<0) return 0;
return frc[x]*inv[y]%mod*inv[x-y]%mod;
}
inline ll fpow(ll x, int y){
ll ret=1;
while(y){
if(y&1) ret=ret*x%mod;
x=x*x%mod; y>>=1;
}
return ret;
}
inline ll plu(ll x, ll y){x+=y; return x>=mod?x-mod:x;}
inline ll sub(ll x, ll y){x-=y; return x<0?x+mod:x;}
ll f[N][2];
ll a[N], b[N];
int main(){
// freopen("D:\\nya\\acm\\B\\1002.in","r",stdin);
// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
frc[0]=1;
for(int i=1; i<N; ++i) frc[i]=frc[i-1]*i%mod;
inv[N-1]=fpow(frc[N-1], mod-2);
for(int i=N-2; i>=0; --i) inv[i]=inv[i+1]*(i+1)%mod;
read(T);
while(T--){
read(n);
ll ans=1;
for(int i=1; i<=n; ++i) {
read(a[i]), read(b[i]);
if(a[i]==0&&b[i]==0) {
--i; --n; continue;
}
f[i][0]=f[i][1]=0;
}
a[n+1]=b[n+1]=0;
f[1][0]=(a[1]!=0); f[1][1]=(b[1]!=0);
for(int i=1; i<=n; ++i){
// if(a[i]){
--a[i];
ans=plu(ans, (ll)sub(C(a[i]+b[i]+2, a[i]+1), 1)*f[i][0]%mod);
++a[i];
// }
// if(b[i]){
--b[i];
ans=plu(ans, (ll)sub(C(a[i]+b[i]+2, b[i]+1), 1)*f[i][1]%mod);
++b[i];
// }
// if(a[i+1]){
// if(a[i]){
--a[i];
f[i+1][0]=plu(f[i+1][0], (ll)f[i][0]*C(a[i]+b[i]+1, b[i])%mod);
++a[i];
// }
// if(b[i]){
--b[i];
f[i+1][0]=plu(f[i+1][0], (ll)f[i][1]*C(a[i]+b[i]+1, b[i])%mod);
++b[i];
// }
// }
// if(b[i+1]){
// if(a[i]){
--a[i];
f[i+1][1]=plu(f[i+1][1], (ll)f[i][0]*C(a[i]+b[i]+1, a[i])%mod);
++a[i];
// }
// if(b[i]){
--b[i];
f[i+1][1]=plu(f[i+1][1], (ll)f[i][1]*C(a[i]+b[i]+1, a[i])%mod);
++b[i];
// }
// }
}
printf("%lld\n", ans);
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(7) 1003 自动人偶
很厉害的dp。
先考虑怎么判定某个串是合法的,考虑极限情况,\(T^{\infty}\) 一定会匹配到 \(S\) 的固定的某个位置,那么在 \(T^{\infty}\) 后面再接上一个串肯定还是匹配到这个位置。
所以可以用这样的方法判定:假设 \(T^{\infty}\) 匹配到的位置是 \(p\),从 \(p\) 开始用kmp再匹配一个 \(T\),如果匹配完成的位置还是 \(p\),那么就找到了 \(T^{\infty}\) 匹配的位置。
接下来是满足机器人不会毁坏的要求,一种思路是找某个机器人的所有不会毁坏的指令,另一种是找某个指令对应的不会毁坏的机器人,以下讲第二种。
我们可以求出指令的最大子段和,如果最大子段和超过了 \(n\) ,那么这个指令一定不合法。
可以在dp过程中实时维护最大前缀和和最大后缀和,要求任意时刻两者都不能大于等于 \(n\),dp完后合法状态需要有两者之和小于 \(n\)。
同时,还需要指令总和小于等于0。
对于一个合法指令,设最大前缀和是 \(x\),那么 \(1\) 到 \(n-x\) 都能在这条指令下存活。
算一下复杂度:
外层枚举假设匹配的位置是 \(O(|S|)\),内层状态数是 \(O(mn^3|S|)\),转移枚举左或右是 \(O(1)\),所以总时间复杂度是 \(O(mn^3|S|^2)\)。
队友用 \(O(mn^2|S|^2)\) 的做法爆标了,记录一下:
设在某一指令下 \(i\) 最终到达了 \(p_i\)(中途如果致敬了传奇机长佐巴杨,那么 \(p_i\) 赋值为 \(n+1\) 不再改变),那么显然 \(p_i\) 是单调不降的,所以存在一个分界点满足 \(p_i\) 合法但是 \(p_{i+1}\) 不合法。
那么我们对于每个点考虑 \(p_i\) 合法但是 $p_{i+1} 不合法的指令数量,乘上 \(i\) 累加起来就是答案。
外层仍然 \(O(|S|)\) 枚举假设的匹配位置,内层先枚举 \(i\),再 \(O(m)\) 枚举指令,状态为 \(f_{cur,i,p_i,p_{i+1}}\),状态数是 \(O(n^3|S|)\),转移 \(O(1)\) 直接做复杂度与上面的算法是一样的。
再观察到相邻的 \(p_i\) 的差只会是0或1,所以状态数可以少一个 \(n\),复杂度做到 \(O(mn^2|S|^2)\)
一个弱化版的题:CF1038F
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int mod=1e9+7;
int T, n, m, len;
char s[35];
int nxt[35];
int to[35][2];
inline ll fpow(ll x, int y){
ll ret=1;
while(y){
if(y&1) ret=ret*x%mod;
x=x*x%mod; y>>=1;
}
return ret;
}
ll f[45][35][35][3];
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
read(n); read(m);
scanf("%s", s+1); len=strlen(s+1);
memset(nxt, 0, sizeof nxt);
memset(to, 0, sizeof to);
int it=0;
for(int i=2; i<=len; ++i) {
while(it&&s[it+1]!=s[i]) it=nxt[it];
if(s[it+1]==s[i]) ++it;
nxt[i]=it;
}
for(int i=0; i<len; ++i){
if(s[i+1]=='L') {
to[i][0]=i+1; to[i][1]=to[nxt[i]][1];
}
else{
to[i][1]=i+1; to[i][0]=to[nxt[i]][0];
}
}
ll ans=0;
for(int i=1; i<=n; ++i){
for(int p=0; p<len; ++p){
memset(f, 0, sizeof f);
if(i<n) f[0][p][i][1]=1;
else f[0][p][i][2]=1;
for(int x=0; x<m; ++x){
for(int c=0; c<len; ++c){
for(int j=1; j<=n; ++j){
int p1=j, p2=j;
f[x+1][to[c][0]][max(1, p1-1)][max(1, p2-1)-max(1, p1-1)]+=f[x][c][j][0];
if(p1+1<=n){
if(p2+1>n) f[x+1][to[c][1]][p1+1][2]+=f[x][c][j][0];
else f[x+1][to[c][1]][p1+1][0]+=f[x][c][j][0];
}
p1=j, p2=j+1;
f[x+1][to[c][0]][max(1, p1-1)][max(1, p2-1)-max(1, p1-1)]+=f[x][c][j][1];
if(p1+1<=n){
if(p2+1>n) f[x+1][to[c][1]][p1+1][2]+=f[x][c][j][1];
else f[x+1][to[c][1]][p1+1][1]+=f[x][c][j][1];
}
p1=j;
f[x+1][to[c][0]][max(1, p1-1)][2]+=f[x][c][j][2];
if(p1+1<=n) f[x+1][to[c][1]][p1+1][2]+=f[x][c][j][2];
}
}
}
for(int j=1; j<=i; ++j) ans+=f[m][p][j][2]*i;
}
}
// cout<<ans<<endl;
printf("%lld\n", fpow((1ll<<m)%mod, mod-2)*(ans%mod)%mod);
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(8) 1010 cats 的集合 1
致敬传奇题目7526之剪枝2log过1e6。
场上差点写完,思路与题解基本一致。
1和5都是trie基本操作,4只需要把为1的位左右子树交换也很容易实现,难点在于2和3。
两种操作本质相同,都是对于特定的位把左右子树合并后放到其中一侧。
直接暴力做复杂度肯定是错的,但是考虑放成懒标记,查询到这棵子树时再考虑更新。
考虑这样做的复杂度,需要势能分析。
合并两棵子树时每次对于左右两个儿子考虑,合并后最多使节点数-1。
总结点数是 \(O((n+m)logV)\),所以只需要考虑合并时不减少节点的合并次数,这样的合并当且仅当两个儿子只存在其中一个时发生。
考虑一次节点数减少的合并会让节点数不减少的合并最多增加一个,1和5操作会产生最多 \(O(logV)\) 个只有一侧儿子的节点,所以节点数不减少的合并也是 \(O((n+m)logV)\) 级别的。
现在只需要考虑怎么设计懒标记,考虑每个节点维护一个三元组 \(\{a,b,c\}\),表示子树内所有点都需要进行 \(x\&a|b{\otimes}c\)(按顺序运算)。
合并时为了保证标记运算封闭,需要能用六个参数 \(\{a,b,c\},\{d,e,f\}\) 表示出 \(\{o,p,q\}\),使得 \(x\&a|b\otimes c\&d|e{\otimes}f=x\&o|p{\otimes}q\)。
一种思路是通过位运算化简,但是我不会,于是我使用了打表压位,先用 \(O(2^6\times 2^3)\) 的时间得出只考虑一个二进制位的表示结果,然后压4位(\(16^6=16777216\),不算很大),每次更新时用 \(O(log_{16}V)\) 的时间枚举更新,这样的话复杂度就是 \(O((n+m)logVlog_{16}V)\)。
但是题目数据不强,仅仅是加上空标记不下方就通过了这道题……
代码
#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 N=2e5+5;
const int full=2147483647;
int T, n, m;
int s[N*32][2], idx;
int tab[16][16][16][16][16][16][3];
int tb[2][2][2][2][2][2][3];
int v[7];
inline void dfs(int x){
if(x==7){
for(int o=0; o<2; ++o){
for(int p=0; p<2; ++p){
for(int q=0; q<2; ++q){
int cnt=0;
for(int t=0; t<2; ++t){
int x=t; x&=v[1]; x|=v[2]; x^=v[3]; x&=v[4]; x|=v[5]; x^=v[6];
if(x==(((t&o)|p)^q)) ++cnt;
}
if(cnt==2) {
tb[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][0]=o;
tb[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][1]=p;
tb[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][2]=q;
return ;
}
}
}
}
}
for(int i=0; i<2; ++i){
v[x]=i; dfs(x+1);
}
}
inline void dfs2(int x){
if(x==7){
for(int i=0; i<4; ++i){
tab[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][0]|=(tb[(v[1]>>i)&1][(v[2]>>i)&1][(v[3]>>i)&1][(v[4]>>i)&1][(v[5]>>i)&1][(v[6]>>i)&1][0])<<i;
tab[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][1]|=(tb[(v[1]>>i)&1][(v[2]>>i)&1][(v[3]>>i)&1][(v[4]>>i)&1][(v[5]>>i)&1][(v[6]>>i)&1][1])<<i;
tab[v[1]][v[2]][v[3]][v[4]][v[5]][v[6]][2]|=(tb[(v[1]>>i)&1][(v[2]>>i)&1][(v[3]>>i)&1][(v[4]>>i)&1][(v[5]>>i)&1][(v[6]>>i)&1][2])<<i;
}
return ;
}
for(int i=0; i<16; ++i){
v[x]=i; dfs2(x+1);
}
}
inline void init(){
dfs(1);
dfs2(1);
}
struct node{
int v1, v2, v3;
inline void clear(){
v1=full; v2=v3=0;
}
}tag[N*32];
inline node plu(node x, node y){
if(x.v1==full&&x.v2==0&&x.v3==0) return y;
node ret; ret.v1=ret.v2=ret.v3=0;
for(int i=0; i<31; i+=4){
ret.v1|=tab[(x.v1>>i)&15][(x.v2>>i)&15][(x.v3>>i)&15][(y.v1>>i)&15][(y.v2>>i)&15][(y.v3>>i)&15][0]<<i;
ret.v2|=tab[(x.v1>>i)&15][(x.v2>>i)&15][(x.v3>>i)&15][(y.v1>>i)&15][(y.v2>>i)&15][(y.v3>>i)&15][1]<<i;
ret.v3|=tab[(x.v1>>i)&15][(x.v2>>i)&15][(x.v3>>i)&15][(y.v1>>i)&15][(y.v2>>i)&15][(y.v3>>i)&15][2]<<i;
}
return ret;
}
inline int gen(){
++idx; s[idx][0]=s[idx][1]=0; tag[idx].clear();
return idx;
}
inline void push_down(int p, int dep);
inline void merge(int &p, int &q, int dep);
int rt;
inline void ins(int x){
int cur=rt;
for(int i=30; i>=0; --i){
push_down(cur, i);
int c=(x>>i)&1;
if(!s[cur][c]) s[cur][c]=gen();
cur=s[cur][c];
}
}
inline int qry(int x){
int cur=rt, ret=0;
for(int i=30; i>=0; --i){
push_down(cur, i);
int c=(x>>i)&1;
if(s[cur][c^1]){
ret|=(1<<i); cur=s[cur][c^1];
}
else cur=s[cur][c];
}
return ret;
}
int main(){
// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
init();
fin>>T;
while(T--){
fin>>n>>m;
idx=0; rt=gen();
for(int i=1, x; i<=n; ++i) fin>>x, ins(x);
while(m--){
int op, x; fin>>op>>x;
if(op==1) ins(x);
else if(op==2){
tag[rt]=plu(tag[rt], (node){full, x, 0});
}
else if(op==3){
tag[rt]=plu(tag[rt], (node){x, 0, 0});
}
else if(op==4){
tag[rt]=plu(tag[rt], (node){full, 0, x});
}
else fout<<qry(x), fout.pc('\n');
}
}
return 0;
}
inline void merge(int &p, int &q, int dep){
if(!p||!q) {
p=p+q; q=0; return ;
}
if(dep==-1) {
q=0;
return ;
}
push_down(p, dep); push_down(q, dep);
merge(s[p][0], s[q][0], dep-1);
merge(s[p][1], s[q][1], dep-1);
q=0;
}
inline void push_down(int p, int dep){
if(tag[p].v1==full&&tag[p].v2==0&&tag[p].v3==0) return ;
if(!((tag[p].v1>>dep)&1)) merge(s[p][0], s[p][1], dep-1);
if((tag[p].v2>>dep)&1) merge(s[p][1], s[p][0], dep-1);
if((tag[p].v3>>dep)&1) swap(s[p][0], s[p][1]);
if(s[p][0]) tag[s[p][0]]=plu(tag[s[p][0]], tag[p]);
if(s[p][1]) tag[s[p][1]]=plu(tag[s[p][1]], tag[p]);
tag[p].clear();
}
2024“钉耙编程”中国大学生算法设计超级联赛(9) 1008 最佳选手
先考虑对某个人 \(x\) 的判定,对于 \(x\) 参与的所有场,让 \(x\) 拿 \(a+z\) 分肯定更容易获胜,所以 \(x\) 的最终得分 \(val_x\) 是确定的。
至于其他人,则需要合理分配 \(z\) 使得满足题目要求,容易讨论得到不同情况 \(z\) 的分配方式是唯一的,就是题解中这个表。

这时候,可以把问题转化为图论问题:一条边连接两个顶点(可以相同)表明这条边可以让两个顶点之一被覆盖,按照上文中方式加边后,求是否存在每个点都被覆盖的方案。
容易发现只需要每个联通块不是树即可,换而言之就是联通块边数大于等于点数。
容易发现一条边会经历三次变化,变化形式相同且边界递增,于是可以考虑把所有人按 \(val\) 排序后逐个去做。
在3到4变化时,会涉及到断边,但容易发现断边后一定会使两个块不是树,所以这条边没必要真正断开。
于是只需要并查集就可以维护了,复杂度瓶颈在于对人和边变化边界的排序 \(O(nlogn)\),上述操作是并查集复杂度均摊 \(O(n\alpha(n))\)。
代码
#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 int uint;
typedef unsigned long long ull;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=1e6+5;
int Test, n, m;
int ctree, ist[N];
int qa[N], qb[N], qx[N], qy[N], qz[N], lim[N];
int fa[N], sz[N], cc[N];
int val[N], rk[N];
inline int gf(int x){
if(x==fa[x]) return fa[x];
return fa[x]=gf(fa[x]);
}
inline void add(int x){
ctree-=ist[x];
++cc[x];
ist[x]=sz[x]>cc[x];
ctree+=ist[x];
}
inline void del(int x){
ctree-=ist[x];
--cc[x];
ist[x]=sz[x]>cc[x];
ctree+=ist[x];
}
inline void merge(int x, int y){
x=gf(x); y=gf(y);
if(x==y) return add(x);
fa[y]=x;
ctree-=ist[x]; ctree-=ist[y];
sz[x]+=sz[y]; cc[x]+=cc[y]; ++cc[x];
ist[x]=sz[x]>cc[x];
ctree+=ist[x];
}
inline bool cmp(int x, int y){
return val[x]<val[y];
}
struct node{
int tp, x, y, cur;
inline bool operator <(const node &t)const{
if(cur^t.cur) return cur<t.cur;
return tp<t.tp;
}
};
inline void work(node t){
if(t.tp==1) {
add(gf(t.x));
}
if(t.tp==2){
del(gf(t.x));
merge(t.x, t.y);
}
if(t.tp==3){
add(gf(t.x));
}
}
vector<int> bin1[N], bin2[N];
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(Test);
while(Test--){
read(n); read(m);
for(int i=1; i<=n; ++i) fa[i]=rk[i]=i, sz[i]=1, ist[i]=1, cc[i]=0, val[i]=INT_MAX, bin1[i].clear(), bin2[i].clear();
for(int i=1; i<=m; ++i) {
read(qa[i]), read(qb[i]), read(qx[i]), read(qy[i]), read(qz[i]);
if(qx[i]>qy[i]) swap(qa[i], qb[i]), swap(qx[i], qy[i]);
val[qa[i]]=min(val[qa[i]], qx[i]+qz[i]);
val[qb[i]]=min(val[qb[i]], qy[i]+qz[i]);
lim[i]=max((uint)qy[i], ((uint)qx[i]+(uint)qy[i]+(uint)qz[i]+1u)/2u);
bin1[qa[i]].emplace_back(i); bin2[qb[i]].emplace_back(i);
}
ctree=n;
sort(rk+1, rk+n+1, cmp);
vector<int> ans;
vector<node> vec;
for(int i=1; i<=m; ++i){
vec.emplace_back((node){1, qa[i], qb[i], qx[i]});
vec.emplace_back((node){2, qa[i], qb[i], qy[i]});
vec.emplace_back((node){3, qa[i], qb[i], lim[i]});
}
sort(vec.begin(), vec.end());
for(int _=1, it=0; _<=n; ++_){
int i=rk[_];
while(it<(int)vec.size()&&vec[it].cur<val[i]){
work(vec[it]);
++it;
}
for(auto t:bin1[i]){
if(qy[t]<val[i]) add(gf(qb[t]));
}
for(auto t:bin2[i]){
if(qx[t]<val[i]) add(gf(qa[t]));
}
add(gf(i));
// cout<<ctree<<' '<<i<<endl;
if(ctree==0) ans.emplace_back(i);
for(auto t:bin1[i]){
if(qy[t]<val[i]) del(gf(qb[t]));
}
del(gf(i));
for(auto t:bin2[i]){
if(qx[t]<val[i]) del(gf(qa[t]));
}
}
sort(ans.begin(), ans.end());
printf("%d\n", (int)ans.size());
for(auto t:ans) printf("%d ", t);
putchar('\n');
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(9) 1009 长期素食
shaber结论题,猜对了但是没完全对。
如果没有连续两天不同的限制,直接李超树就行了,但现在有了这个限制,一个简单的想法是求出每个位置的最大和次大函数,转移时 \(O(4)\) 讨论一下。
至于怎么求次大,可以先求最大的,然后把每个函数在所以作为最大函数的地方断开,可以发现这还是 \(O(n)\) 个函数,再跑一下李超树就行了(非常粗暴。
但是场上交上去wa了,一直以为是李超树挂了。
补题的时候再多预处理了第3大函数就过了。
没有找到严谨证明,我个人认为不应该是前3大,应该是把所有最大和次大(也就是函数值不去重)都拿在一起考虑,我场上是这样想的,这个总数量可以证明是 \(O(n\sqrt n)\) 的,所以我觉得应该是这样做。
数量级考虑每种斜率函数只保留最大的两个,那么总交点数量是 \(O(n^2)\),考虑每一个位置最大值有 \(t\) 个函数,那么会用掉 \(O(t^2)\) 个交点,用些数学知识就能推出 \(\sum t=O(n\sqrt n)\),次大值同理。
但是我不知道怎么在低于 \(O(n\sqrt n)\) 的时间内找到这些函数,感觉很困难。
upd
证明是简单的,因为对于某一天,这天和前一天、后一天的都不能一样,所以需要维护前三大。
对于我场上的问题的改版,在 Larunatrecy 的帮助下得到了一个 \(O(nlogn)\) 的解法。
改版后的问题是有 \(n\) 个一次函数,多次询问某个横坐标上最大和严格次大函数值。
具体的,类似斜率优化求出来上凸壳,然后维护前缀李超和后缀李超,再额外对不在凸壳上的维护一个李超,求严格次大值时把凸壳上的最大值删掉,容易发现删除的是凸壳的一段区间,那剩下的是一个前后缀加上不在凸壳上的点,可以做到 \(O(nlogn)\)。
如果可以做在线半平面数点的话,甚至可以解决非严格第 \(k\) 大函数值。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e6+5;
int T, n, m;
int va[N], vb[N];
int id[N][3];
struct data{
int k, b;
}a[N];
int cnt;
int calc(int id, int x){
return a[id].k*x+a[id].b;
}
int mx[N<<2];
void add(int sx, int sy, int ex, int ey){
cnt++;
if(sx==ex){
a[cnt].k=0;
a[cnt].b=max(sy, ey);
}
else{
a[cnt].k=1.0*(ey-sy)/(ex-sx);
a[cnt].b=-a[cnt].k*sx+sy;
}
}
void upd(int p, int l, int r, int x){
int mid=(l+r)>>1, &y=mx[p];
if(calc(x, mid)>calc(y, mid)) swap(x, y);
if(calc(x, l)>calc(y, l)) upd(p<<1, l, mid, x);
if(calc(x, r)>calc(y, r)) upd(p<<1|1, mid+1, r, x);
}
void mdf(int p, int l, int r, int L, int R, int x){
if(L<=l&&r<=R){
upd(p, l, r, x);
return ;
}
int mid=(l+r)>>1;
if(L<=mid) mdf(p<<1, l, mid, L, R, x);
if(R>mid) mdf(p<<1|1, mid+1, r, L, R, x);
}
pii cmp(pii x, pii y){
if(x.fi<y.fi) return y;
if(x.fi>y.fi) return x;
return x.se<y.se?x:y;
}
pii get(int p, int l, int r, int x){
if(r<x||x<l) return mapa(0, 0);
int mid=(l+r)>>1;
pii ret=mapa(calc(mx[p], x), mx[p]);
if(l==r) return ret;
return cmp(ret, cmp(get(p<<1, l, mid, x), get(p<<1|1, mid+1, r, x)));
}
int rev[N];
vector<int> vec[N];
ll f[N][3];
vector<int> del[N];
int main(){
// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
read(T);
while(T--){
read(n); read(m);
for(int i=1; i<=m; ++i) vec[i].clear(), f[i][0]=f[i][1]=f[i][2]=0;
for(int i=1; i<=10000; ++i) id[i][0]=id[i][1]=id[i][2]=0;
for(int i=1, x, y; i<=n; ++i){
read(x); read(y); va[i]=x; vb[i]=y; del[i].clear();
if(x>va[id[y][0]]) id[y][2]=id[y][1], id[y][1]=id[y][0], id[y][0]=i;
else if(x>va[id[y][1]]) id[y][2]=id[y][1], id[y][1]=i;
else if(x>va[id[y][2]]) id[y][2]=i;
}
cnt=0;
for(int i=1; i<=m*4; ++i) mx[i]=0;
for(int i=1; i<=10000; ++i) for(int j=0; j<3; ++j) if(id[i][j]) {
add(1, va[id[i][j]]+vb[id[i][j]], m, va[id[i][j]]+vb[id[i][j]]*m);
mdf(1, 1, m, 1, m, cnt);
rev[cnt]=id[i][j];
}
for(int i=1; i<=m; ++i) {
int t=rev[get(1, 1, m, i).se];
vec[i].emplace_back(t);
del[t].emplace_back(i);
}
cnt=0;
for(int i=1; i<=m*4; ++i) mx[i]=0;
for(int i=1; i<=10000; ++i) for(int j=0; j<3; ++j) if(id[i][j]) {
int t=id[i][j];
int lst=1; del[t].emplace_back(m+1);
sort(del[t].begin(), del[t].end());
for(auto c:del[t]){
if(lst<=c-1){
add(lst, va[t]+vb[t]*lst, c-1, va[t]+vb[t]*(c-1));
mdf(1, 1, m ,lst, c-1, cnt);
rev[cnt]=t;
}
lst=c+1;
}
del[t].pop_back();
}
for(int i=1; i<=m; ++i) {
int t=rev[get(1, 1, m, i).se];
vec[i].emplace_back(t);
del[t].emplace_back(i);
}
cnt=0;
for(int i=1; i<=m*4; ++i) mx[i]=0;
for(int i=1; i<=10000; ++i) for(int j=0; j<3; ++j) if(id[i][j]) {
int t=id[i][j];
int lst=1; del[t].emplace_back(m+1);
sort(del[t].begin(), del[t].end());
for(auto c:del[t]){
if(lst<=c-1){
add(lst, va[t]+vb[t]*lst, c-1, va[t]+vb[t]*(c-1));
mdf(1, 1, m ,lst, c-1, cnt);
rev[cnt]=t;
}
lst=c+1;
}
}
for(int i=1; i<=m; ++i) {
int t=rev[get(1, 1, m, i).se];
vec[i].emplace_back(t);
}
for(int i=0; i<3; ++i) f[1][i]=va[vec[1][i]]+vb[vec[1][i]];
for(int i=2; i<=m; ++i){
for(int j=0; j<3; ++j) if(vec[i-1][j]){
for(int k=0; k<3; ++k) if(vec[i][k]) {
if(vec[i-1][j]!=vec[i][k]){
f[i][k]=max(f[i][k], f[i-1][j]+va[vec[i][k]]+vb[vec[i][k]]*i);
}
}
}
}
printf("%lld\n", max(max(f[m][0], f[m][1]), f[m][2]));
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(9) 1011 地牢谜题:更高还是更低
两个问题等效,只考虑L怎么做。
一种暴力的做法是求出来 \(f_{l,r}\in\{0,1\}\) 表示值大于 \(r\) 的数都被删除后是否能一次清空值域在 \([l,r]\) 的所有数,查询时做一个dp即可。
考虑优化,对于某个 \(r\),我们求出最小的合法的 \(l\),设为 \(g_r\),那么查询相当于有一个变量 \(x\),初始为 \(r\),每次让 \(x=g_x-1\),当 \(x\leq l\) 时结束,输出变化次数就是答案。
求答案显然可以用倍增解决,所以接下来要考虑优化求 \(g\) 的过程。
打表可以发现,对于固定的 \(r\),合法的 \(l\) 并没有什么规律,但对于固定的 \(l\),合法的 \(r\) 一定是一段前缀,这个证明只需要考虑如果一个 \(r\) 符合要求,一定可以通过删除若干极大值得到更小的合法值域区间。
所以只需要two-pointer加上单修区查DS就可以在 \(O(nlogn)\) 的时间内解决求 \(g\),查询区间还需要一个区间rmq,也可以用st表做到 \(O(nlogn)-O(1)\) 。
综上,复杂度是 \(O((n+m)logn)\)。
代码
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e5+5;
int T, n, m, k;
int p[N], a[N];
struct BIT{
int tr[N];
inline void clr(){
for(int i=1; i<=n; ++i) tr[i]=0;
}
inline void add(int x, int v){
for(; x<=n; x+=(x&-x)) tr[x]+=v;
}
inline int get(int x){
int ret=0;
for(; x; x-=(x&-x)) ret+=tr[x];
return ret;
}
inline int sum(int l, int r){
return get(r)-get(l-1);
}
};
struct solveL{
BIT t;
int jmp[N], f[N][20];
int st[2][N][20], lg[N];
inline int gmin(int l, int r){
int t=lg[r-l+1];
return min(st[0][l][t], st[0][r-(1<<t)+1][t]);
}
inline int gmax(int l, int r){
int t=lg[r-l+1];
return max(st[1][l][t], st[1][r-(1<<t)+1][t]);
}
inline void init(){
for(int i=1; i<=n; ++i) jmp[i]=i;
for(int i=1; i<=n; ++i) st[0][i][0]=st[1][i][0]=a[i];
for(int i=2; i<=n; ++i) lg[i]=lg[i>>1]+1;
for(int t=1; t<=lg[n]; ++t){
for(int i=1; i+(1<<t)-1<=n; ++i){
st[0][i][t]=min(st[0][i][t-1], st[0][i+(1<<(t-1))][t-1]);
st[1][i][t]=max(st[1][i][t-1], st[1][i+(1<<(t-1))][t-1]);
}
}
t.clr();
int it=0;
for(int i=1; i<=n; ++i){
while(it<i) ++it, t.add(a[it], 1);
while(it-i+1<k&&it<n){
++it; t.add(a[it], 1);
if(t.sum(gmin(i, it), gmax(i, it))>it-i+1){
t.add(a[it], -1); --it; break;
}
}
jmp[it]=min(i, jmp[it]);
}
for(int i=n-1; i>=1; --i) jmp[i]=min(jmp[i], jmp[i+1]);
for(int i=1; i<=n; ++i) f[i][0]=jmp[i]-1;
for(int t=1; t<=lg[n]; ++t){
for(int i=1; i<=n; ++i){
f[i][t]=f[f[i][t-1]][t-1];
}
}
}
inline int ask(int l, int r){
int x=r, ret=1;
for(int t=lg[n]; t>=0; --t){
if(f[x][t]>=l) ret+=(1<<t), x=f[x][t];
}
return ret;
}
}L;
struct solveH{
BIT t;
int jmp[N], f[N][20];
int st[2][N][20], lg[N];
inline int gmin(int l, int r){
int t=lg[r-l+1];
return min(st[0][l][t], st[0][r-(1<<t)+1][t]);
}
inline int gmax(int l, int r){
int t=lg[r-l+1];
return max(st[1][l][t], st[1][r-(1<<t)+1][t]);
}
inline void init(){
for(int i=1; i<=n; ++i) jmp[i]=i;
for(int i=1; i<=n; ++i) st[0][i][0]=st[1][i][0]=a[i];
for(int i=2; i<=n; ++i) lg[i]=lg[i>>1]+1;
for(int t=1; t<=lg[n]; ++t){
for(int i=1; i+(1<<t)-1<=n; ++i){
st[0][i][t]=min(st[0][i][t-1], st[0][i+(1<<(t-1))][t-1]);
st[1][i][t]=max(st[1][i][t-1], st[1][i+(1<<(t-1))][t-1]);
}
}
t.clr();
int it=n+1;
for(int i=n; i>=1; --i){
while(it>i) --it, t.add(a[it], 1);
while(i-it+1<k&&it>1){
--it; t.add(a[it], 1);
if(t.sum(gmin(it, i), gmax(it, i))>i-it+1){
t.add(a[it], -1); ++it; break;
}
}
jmp[it]=max(i, jmp[it]);
}
for(int i=2; i<=n; ++i) jmp[i]=max(jmp[i], jmp[i-1]);
for(int i=1; i<=n; ++i) f[i][0]=jmp[i]+1;
f[n+1][0]=n+1;
for(int t=1; t<=lg[n]; ++t){
for(int i=1; i<=n; ++i){
f[i][t]=f[f[i][t-1]][t-1];
if(f[i][t]==0) f[i][t]=n+1;
}
}
}
inline int ask(int l, int r){
int x=l, ret=1;
for(int t=lg[n]; t>=0; --t){
if(f[x][t]<=r) ret+=(1<<t), x=f[x][t];
}
return ret;
}
}H;
char op;
int main(){
// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
read(T);
while(T--){
read(n); read(k);
for(int i=1; i<=n; ++i) read(p[i]), a[p[i]]=i;
L.init(); H.init();
read(m);
for(int i=1, l, r; i<=m; ++i){
read(l); read(r); op=getchar();
if(op=='L') printf("%d\n", L.ask(l, r));
if(op=='H') printf("%d\n", H.ask(l, r));
}
}
return 0;
}
2024“钉耙编程”中国大学生算法设计超级联赛(9) 1012 地牢谜题:三个怪人
粘个题意:

把下标统一一下就是 \(a_x=y-b_y\)。
所以一种思路是枚举 \(a_x\) 的值,填若干个 \(a\) 和 \(b\) 中的位置,逐步计数。
那么考虑一下 \(b\) 的范围,相当于钦定若干个 \(i\) 满足 \(y-b_y=i\),转换一下就是 \(y=b_y+i\),所以 \(i\leq y\leq m\),所以需要从大往小枚举钦定的值。
还需要一维去记录填的数字的和,\(a\) 是容易统计的,对于 \(b\),每次会最多新增一个位置,那么填上了就表明我们让部分 \(b_y\) 停止增长,剩下的 \(b_y\) 会继续增长,所以每轮只需要加上还没被钦定的 \(b\) 中下标数量即可。
那么考虑计算复杂度,我是暴力实现的,所以复杂度粗略估计是 \(O(m\sum_{i=1}^{m}(\frac{m}{i}m^{2.5}\sum_{i=1}^{m}\frac{m}{i}))=O(m^{5.5}ln^2m)\)……
这个估计似乎太粗略了,精细一点:
\(O(\sum_{i=1}^{m} \sum_{j=1}^{\frac{m}{i}}(m-i)(m-i\times j)m^{0.5}\frac{m}{i})\),好像估计出来还是很大。
反正打表后 \(n=200,m=200\) 涉及模运算的计算量是2e8左右,使用取模优化就过了。
代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#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;
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=205;
#define ui128 __uint128_t
using u32=uint32_t;
using i32=int32_t;
using u64=uint64_t;
using i64=int64_t;
static u32 _m,m2,inv,r2;
u32 getinv(){
u32 inv=_m;
for(int i=0;i<4;++i) inv*=2-inv*_m;
return inv;
}
struct Mont{
private :
u32 x;
public :
static u32 reduce(u64 x){
u32 y=(x+u64(u32(x)*inv)*_m)>>32;
return i32(y)<0?y+_m:y;
}
Mont(){ ; }
Mont(i32 x):x(reduce(u64(x)*r2)) { }
Mont& operator += (const Mont &rhs) { return x+=rhs.x-m2,i32(x)<0&&(x+=m2),*this; }
Mont& operator -= (const Mont &rhs) { return x-=rhs.x,i32(x)<0&&(x+=m2),*this; }
Mont& operator *= (const Mont &rhs) { return x=reduce(u64(x)*rhs.x),*this; }
friend Mont operator + (Mont x,const Mont &y) { return x+=y; }
friend Mont operator - (Mont x,const Mont &y) { return x-=y; }
friend Mont operator * (Mont x,const Mont &y) { return x*=y; }
i32 get(){
u32 res=reduce(x);
return res>=_m?res-_m:res;
}
};
void Init(int _m) {
::_m=_m,m2=_m*2;
inv=-getinv();
r2=-u64(_m)%_m;
}
int p;
int Test, n, m;
Mont C[N][N];
Mont f[2][N][N][N][2];//cur a b s 0/1
int p1, p2, p3, p4, p5, p6;
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(Test);
while(Test--){
read(n); read(m); read(p); Init(p);
C[0][0]=1;
for(int i=1; i<=200; ++i){
C[i][0]=1;
for(int j=1; j<=i; ++j) C[i][j]=C[i-1][j-1]+C[i-1][j];
}
memset(f, 0, sizeof f);
int lst=0, cur=1;
f[cur][0][0][0][0]=1;
int up=0;
while(up*(up+1)/2<=m) ++up;
for(int i=m; i>=(1-m); --i){
swap(lst, cur);
int lim=min(m, m-i+1);
for(int j=0; j*(i+1)<=m&&j<=n; ++j){//a
if(i<0&&j!=n) continue;
for(int k=0; k<=lim; ++k){//b
for(int s=max(j*(i+1), 0); s<=m; ++s){//sum
if(f[lst][j][k][s][0].get()){
ll v=f[lst][j][k][s][0].get();
if(i>0){
for(int c=max(0, s+lim-k-m); c<=lim-k&&c<=up; ++c){
// for(int c2=0; s+lim-k-c+c2*i<=m&&j+c2<=n; ++c2){
for(int c2=min((m-s-lim+k+c)/i, n-j); c2>=0; --c2){
// ++p1;
if(c>0&&c2==1) continue;
f[cur][j+c2][k+c][s+lim-k-c+c2*i][0]+=v*C[lim-k][c]*C[n-j][c2];
}
}
}
if(i==0){
// for(int c=0; c<=lim-k&&c*(c-1)/2ll<=m; ++c){
for(int c=min(lim-k, up); c>=0; --c){
// ++p2;
if(s+lim-k-c+i<=m&&j+1<=n) f[cur][j+1][k+c][s+lim-k-c+i][1]+=v*C[lim-k][c]*C[n-j][1];
}
}
if(i>0){
// for(int c=1; c<=lim-k&&c*(c-1)/2ll<=m; ++c){
for(int c=min(lim-k, up); c>=1; --c){
// ++p3;
if(s+lim-k-c+i<=m&&j+1<=n) f[cur][j+1][k+c][s+lim-k-c+i][1]+=v*C[lim-k][c]*C[n-j][1];
}
}
f[lst][j][k][s][0]=0;
}
if(f[lst][j][k][s][1].get()){
ll v=f[lst][j][k][s][1].get();
for(int c=max(0, s+lim-k-m); c<=lim-k&&c<=up; ++c){
if(i>0){
// for(int c2=0; s+lim-k-c+c2*i<=m&&j+c2<=n; ++c2){
for(int c2=min((m-s-lim+k+c)/i, n-j); c2>=0; --c2){
// ++p4;
if(c>0&&c2==1) continue;
f[cur][j+c2][k+c][s+lim-k-c+c2*i][1]+=v*C[lim-k][c]*C[n-j][c2];
}
}
else if(i==0){
int c2=n-j;
// ++p5;
if(c2==1) continue;
f[cur][n][k+c][s+lim-k-c+c2*i][1]+=v*C[lim-k][c];
}
else{
// ++p6;
f[cur][j][k+c][s+lim-k-c][1]+=v*C[lim-k][c];
}
}
f[lst][j][k][s][1]=0;
}
}
}
}
}
// cout<<p1<<' '<<p2<<' '<<p3<<' '<<p4<<' '<<p5<<' '<<p6<<endl;
printf("%d\n", f[cur][n][m][m][1].get());
}
return 0;
}

浙公网安备 33010602011771号