2023.10.11~2023.10.17测试
2023.10.11
T1 染色
给定 \(n\),需要给整数 \(1\sim n\) 染色,使得对于所有 \(1\leq i\leq j\leq n\),若 \(j-i\) 为质数,则 \(i,j\) 不同色。求颜色最少的染色方案,输出任意一种方案
\(1\leq n\leq 10000\)
诈骗题
观察到若 \(j=i+4\) 则 \(i,j\) 可同色,所以答案上界为 \(4\),而样例中 \(n=7\) 的答案已经是 \(4\),所以 \(n\ge 7\) 的全都是 \(4\),小于 \(7\) 的打表即可
code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n;
int main()
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
// fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
scanf("%d",&n);
if(n==1)
printf("1\n1");
else if(n==2)
printf("1\n1 1");
else if(n==3)
printf("2\n1 1 2");
else if(n==4)
printf("2\n1 1 2 2");
else if(n==5)
printf("3\n1 1 2 2 3");
else if(n==6)
printf("3\n1 1 2 2 3 3");
else
{
puts("4");
for(int i=1; i<=n; i++)
printf("%d ",i%4+1);
}
return 0;
}
T2 序列
给定一个长度为 \(m\) 的序列 \(a\),有一个长度为 \(m\) 的序列 \(b\),需满足 \(0\leq b_i\leq n\) 且 \(\sum\limits_{i=1}^m {a_ib_i}\leq D\) 且 \(d_i\) 为整数
求 \(\sum\limits_{i=1}^mb_i+k\min\{b_i\}\) 的最大值
\(1\leq T\leq 5\),\(1\leq n\leq 10^9\),\(1\leq k,m\leq 2\times 10^5\),\(1\leq D\leq 10^{18}\),\(1\leq a_i\leq 5000\)
这个 \(\min\) 很难受啊,考虑钦定最小值 \(mn\)
从贪心来说显然是先考虑小的 \(a_i\),将 \(a\) 从小到大排序,令 \(b_m=mn\) 并让 \(b_1\sim b_{m-1}\) 也先填上 \(mn\)。接下来要想最大化 \(\sum b_i\),显然是填的 \(n\) 越多越好(当然也可以证明这样是最优的),所以考虑二分出最后一个填 \(n\) 的位置 \(pos\),那么再令 \(b_{pos+1}\) 填一个最大能填的数即可
所以,在钦定 \(mn\) 后,答案在 \(\mathcal{O}(\log m)\) 的时间内求出。而根据打表可以发现答案是一个关于 \(\min\{b_i\}\) 的非严格单峰函数,所以可以使用三分法求出答案(当然要点特殊方法,但是数据水直接正常三分过了)
综上,突破口在于钦定 \(\min\{b_i\}\),最难的地方在于看出这是一个单峰函数,在考场上应充分使用打表等工具观察性质
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e5+10;
int T,m;
LL n,k,D,a[N],s[N];
int find2(LL mn,LL d)
{
if(m<=2)
return 0;
int lo=0,hi=m-2;
while(lo+1<hi)
{
int mid=(lo+hi)/2;
if((n-mn)*s[mid]<=d)
lo=mid;
else
hi=mid;
}
return lo;
}
LL calc(LL mn)
{
if(mn<0)
return 0;
LL res=mn*m+mn*k,d=D-mn*s[m],add=0;
if(d<0)
return 0;
LL pos=find2(mn,d);
d-=(n-mn)*s[pos];
res+=(n-mn)*pos+min(n-mn,d/a[pos+1]);
return res;
}
LL find(LL l,LL r)
{
LL lo=l,hi=r;
while(lo+2<hi)
{
LL lmid=lo+(hi-lo)/3;
LL rmid=hi-(hi-lo)/3;
if(calc(lmid)>=calc(rmid))
hi=rmid;
else
lo=lmid;
}
return (lo+hi)/2;
}
void mian()
{
scanf("%lld%d%lld%lld",&n,&m,&k,&D);
for(int i=1; i<=m; i++)
scanf("%lld",&a[i]);
sort(a+1,a+1+m);
for(int i=1; i<=m; i++)
s[i]=s[i-1]+a[i];
LL mn=find(0,min(n,D/s[m]));
LL ans=calc(mn);
for(int i=1; i<=10; i++)
ans=max(ans,max(calc(mn+i),calc(mn-i)));
printf("%lld\n",ans);
}
int main()
{
freopen("array.in","r",stdin);
freopen("array.out","w",stdout);
scanf("%d",&T);
while(T--)
mian();
return 0;
}
T3 树上询问
一棵 \(n\) 个节点的树,每次询问给定 \(l,r\),询问存在多少个整数 \(k\),使得从 \(l\) 沿着 \(l\rightarrow r\) 的简单路径走 \(k\) 步恰好走到 \(k\)
\(1\leq n,m\leq 3\times 10^5\)
简单题,但是树上倍增预处理写错了 \(100\rightarrow 5\)
将一条 \(l\rightarrow r\) 的简单路径转化成 \(l\rightarrow lca\rightarrow r\),对于 \(l\rightarrow lca\) 这条路径上的点,需满足 \(dep_l-dep_x=x\) 即 \(dep_x+x=dep_l\)。对于 \(lca\rightarrow r\) 这条路径上的点,需满足 \(dep_l-dep_{lca}+dep_x-dep_{lca}=x\) 即 \(dep_x-x=2\times dep_{lca}-dep_l\)。树上差分+主席树维护即可
code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
bool Mbe;
const int N=3e5+10;
int n,m,a[2][N],b[2][N],rev[2][N],m0,m1;
vector <int> g[N];
namespace Seg
{
int rt[N][2];
struct SegmentTree
{
int lc[N*40],rc[N*40],sum[N*40],tot;
#define lc(x) lc[x]
#define rc(x) rc[x]
#define sum(x) sum[x]
void pushup(int p)
{
sum(p)=sum(lc(p))+sum(rc(p));
}
void build(int &p,int l,int r)
{
p=++tot;
if(l==r)
return sum(p)=0,void();
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
}
void change(int &p,int pre,int l,int r,int x,int v)
{
// cout<<l<<" "<<r<<endl;
p=++tot;
lc(p)=lc(pre); rc(p)=rc(pre); sum(p)=sum(pre);
if(l==r)
return sum(p)+=v,void();
int mid=(l+r)>>1;
if(x<=mid)
change(lc(p),lc(pre),l,mid,x,v);
else
change(rc(p),rc(pre),mid+1,r,x,v);
pushup(p);
}
int ask(int p,int pre,int l,int r,int x)
{
if(!p)
return 0;
if(l==r)
return sum(p)-sum(pre);
int mid=(l+r)>>1;
if(x<=mid)
return ask(lc(p),lc(pre),l,mid,x);
return ask(rc(p),rc(pre),mid+1,r,x);
}
}t0,t1;
}
using namespace Seg;
namespace TR
{
int dep[N],f[N][25];
void dfs1(int x,int fa)
{
dep[x]=dep[fa]+1;
for(int y:g[x])
{
if(y==fa)
continue;
f[y][0]=x;
for(int i=1; i<=20; i++)
f[y][i]=f[f[y][i-1]][i-1];
dfs1(y,x);
}
}
void dfs2(int x,int fa)
{
t0.change(rt[x][0],rt[fa][0],1,m0,a[0][x],1);
t1.change(rt[x][1],rt[fa][1],1,m1,a[1][x],1);
for(int y:g[x])
if(y!=fa)
dfs2(y,x);
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
for(int i=20; i>=0; i--)
if(dep[f[x][i]]>=dep[y])
x=f[x][i];
if(x==y)
return x;
for(int i=20; i>=0; i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
}
using namespace TR;
bool Med;
int main()
{
// fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
freopen("query.in","r",stdin);
freopen("query.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,0);
for(int i=1; i<=n; i++)
{
a[0][i]=b[0][i]=dep[i]+i;
a[1][i]=b[1][i]=dep[i]-i;
}
sort(b[0]+1,b[0]+1+n);
sort(b[1]+1,b[1]+1+n);
m0=unique(b[0]+1,b[0]+1+n)-(b[0]+1);
m1=unique(b[1]+1,b[1]+1+n)-(b[1]+1);
for(int i=1; i<=n; i++)
{
int x=lower_bound(b[0]+1,b[0]+1+m0,a[0][i])-b[0];
rev[0][x]=a[0][i]; a[0][i]=x;
int y=lower_bound(b[1]+1,b[1]+1+m1,a[1][i])-b[1];
rev[1][y]=a[1][i]; a[1][i]=y;
}
t0.build(rt[0][0],1,m0);
t1.build(rt[0][1],1,m1);
dfs2(1,0);
while(m--)
{
int l,r,lca,ans=0;
scanf("%d%d",&l,&r);
lca=LCA(l,r);
int x=lower_bound(b[0]+1,b[0]+1+m0,dep[l])-b[0];
if(b[0][x]==dep[l])
ans+=t0.ask(rt[l][0],rt[lca][0],1,m0,x);
int y=lower_bound(b[1]+1,b[1]+1+m1,2*dep[lca]-dep[l])-b[1];
if(b[1][y]==2*dep[lca]-dep[l])
ans+=t1.ask(rt[r][1],rt[lca][1],1,m1,y);
if(dep[l]-dep[lca]==lca)
ans++;
printf("%d\n",ans);
}
return 0;
}
T4 莫队
(与莫队无关)
给出一个长度为 \(n\) 的序列 \(a\),支持单点修改,或者查询区间 \([l,r]\) 内有多少个无重的子区间
\(n,m\leq 2\times 10^5\)
设 \(f_i\) 表示 \(i\) 往右第一次出现 \(a_i\) 的位置,那么一个区间 \([l,r]\) 的,当且仅当 \(r<\min\limits_{i=l}^r\{f_i\}\)
一个区间 \([l,r]\) 的合法子区间个数为
后缀 \(\min\) 优秀的性质使得我们可以用线段树维护区间后缀 \(\min\) 的和
类似于 P4198 楼房重建,合并两个区间时,\(dat(rc(p))\) 可以直接累加,但是对于左区间来说,可能会存在一段是大于 \(mn(rc(p))\) 的,那么这一段的贡献以 \(mn(rc(p))\) 累计,剩下的一段就是原来的 \(dat\)。
具体地,设 \(merge(val,p,l,r)\) 表示当右边最小值为 \(val\) 时区间 \([l,r]\) 的贡献。那么如果此时的右区间最小值已经小于 \(val\),那断点肯定在右区间里,递归右区间即可
否则,右区间贡献以 \(val\) 累计,递归左区间即可
还要用个 set 维护 \(f_i\)
code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e5+10;
int n,m,a[N],f[N],hi;
LL res;
set <int> s[N];
#define lc(p) p<<1
#define rc(p) p<<1|1
struct SegmentTree
{
int mn; LL dat;
#define mn(x) tree[x].mn
#define dat(x) tree[x].dat
}tree[N<<2];
LL merge(int val,int p,int l,int r)
{
if(l==r)
return min(dat(p),(LL)val);
int mid=(l+r)>>1;
if(mn(rc(p))<val)
return dat(p)-dat(rc(p))+merge(val,rc(p),mid+1,r);
return merge(val,lc(p),l,mid)+1LL*val*(r-mid);
}
void pushup(int p,int l,int r)
{
mn(p)=min(mn(lc(p)),mn(rc(p)));
dat(p)=dat(rc(p))+merge(mn(rc(p)),lc(p),l,(l+r)>>1);
}
void build(int p,int l,int r)
{
if(l==r)
{
dat(p)=mn(p)=f[l];
return;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
pushup(p,l,r);
}
void change(int p,int l,int r,int x,int v)
{
if(l==r)
{
dat(p)=mn(p)=v;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
change(lc(p),l,mid,x,v);
else
change(rc(p),mid+1,r,x,v);
pushup(p,l,r);
}
void ask(int p,int l,int r,int ql,int qr)
{
if(ql<=l && qr>=r)
{
res+=merge(hi,p,l,r);
hi=min(hi,mn(p));
return;
}
int mid=(l+r)>>1;
if(qr>mid)
ask(rc(p),mid+1,r,ql,qr);
if(ql<=mid)
ask(lc(p),l,mid,ql,qr);
}
int main()
{
freopen("team.in","r",stdin);
freopen("team.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]),s[i].insert(n+1);
for(int i=n; i>=1; i--)
f[i]=*(s[a[i]].begin()),s[a[i]].insert(i);
build(1,1,n);
while(m--)
{
int op,l,r,x,y;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&x,&y);
auto it=s[a[x]].find(x);
int nxt=*(++it); it--;
if(it!=s[a[x]].begin())
it--,change(1,1,n,*it,nxt);
s[a[x]].erase(x); a[x]=y;
it=s[y].lower_bound(x);
change(1,1,n,x,*it);
if(it!=s[y].begin())
it--,change(1,1,n,*it,x);
s[y].insert(x);
}
else
{
scanf("%d%d",&l,&r);
res=0; hi=r+1;
ask(1,1,n,l,r);
res-=1LL*(r-l+1)*(l+r)/2;
printf("%lld\n",res);
}
}
return 0;
}
\(100+0+5+0=105\),纯唐
2023.10.12
联考?666
T1 异或
有一个 \(n\times n\) 的矩阵 \(A\),初始所有元素均为 \(0\)。
执行 \(q\) 次如下的操作,每次操作形如 \((r,c,l,s)\),即对于 \(x\in[r,r+l),y\in[c,c+x-r]\) 的元素 \((x,y)\) 加上 \(s\),求最后矩阵元素的异或和
\(n\leq 10^3\),\(q\leq 3\times 10^5\)
就是给一个等腰直角三角形全部加上 \(s\),写个差分没了
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e3+10;
int n,q;
LL b[N][N],d[N][N],ans;
int main()
{
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
scanf("%d%d",&n,&q);
while(q--)
{
int r,c,l,s,tx=0,ty=0;
scanf("%d%d%d%d",&r,&c,&l,&s);
tx=r+l-1; ty=tx-r+c;
d[r][c]+=(LL)s; d[tx+1][c]-=(LL)s;
b[tx+1][c+1]-=(LL)s; b[tx+1][ty+2]+=(LL)s;
}
for(int i=0; i<=n+2; i++)
{
for(int j=1; j<=n+2; j++)
d[j][i]+=d[j-1][i];
}
for(int i=0; i<=n+2; i++)
{
for(int j=1; j<=n+2; j++)
b[i][j]+=b[i][j-1];
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
d[i][j]+=d[i-1][j-1]+b[i][j];
ans^=d[i][j];
}
}
printf("%lld",ans);
return 0;
}
T2 游戏
Alice 和 Bob 在博弈,共 \(m\) 轮,两人轮流操作
一开始有集合 \(A=\{a_1,a_2,\dots,a_n\}\),每轮操作有两种选择,保留 \(A\) 中所有 \(b_i\) 的倍数,或者保留 \(A\) 中所有非 \(b_i\) 的倍数。
Alice 想要让最后 \(A\) 中的数字最小,Bob 想要让其最大,求最后的答案
\(1\leq n\leq 2\times 10^4\),\(1\leq m\leq 2\times 10^5\),$ -10^{14}\leq a_i\leq 10^{14}$,\(1\leq b_i\leq 10^{14}\)
博弈一点不会好吧。写个部分分还因为判断条件写错挂成 \(0\) 分
\(\mathcal{O}(n2^m)\) 比较好想,复杂度高在于有很多无用状态。因为每个元素在一次操作后只会分向一边,所以考虑拿一个二叉树维护这个操作。建立一个 \(m\) 层的二叉树,如果 \(a_i\) 是 \(b_{dep}\) 的倍数就往左边插入,否则往右边插入。时间复杂度 \(\mathcal{O}(nm)\)
最后我们需要一点观察,我们发现每次操作中如果先手选择大的那一侧,那么至多 \(\log n\) 次后答案就会变成 \(0\),后手同理。又因为先后手轮流操作,所以当 \(m>2\log n\) 时,答案必为 \(0\),这样复杂度就是 \(\mathcal{O}(n\log n)\) 的
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e4+10,M=2e5+10;
int n,m,tot,rt;
LL a[N],b[M];
struct Tree
{
int lc,rc; LL sum;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#define sum(x) tree[x].sum
}tree[N<<2];
void insert(int &p,int dep,LL x)
{
if(!p)
p=++tot;
if(dep==m+1)
return sum(p)+=x,void();
if(x%b[dep]==0)
insert(lc(p),dep+1,x);
else
insert(rc(p),dep+1,x);
}
LL query(int p,int dep)
{
if(!p)
return 0;
if(dep==m+1)
return sum(p);
LL lval=query(lc(p),dep+1);
LL rval=query(rc(p),dep+1);
if(dep&1)
return min(lval,rval);
return max(lval,rval);
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%lld",&a[i]);
for(int i=1; i<=m; i++)
scanf("%lld",&b[i]);
if(m>2*(log2(n)+1))
{
printf("0");
return 0;
}
for(int i=1; i<=n; i++)
insert(rt,1,a[i]);
printf("%lld",query(rt,1));
return 0;
}
T3 联通块
一个 \(n\) 个点的图,点权 $\rm gcd $ 为合数的点之间连边。求一个点后剩下的最大联通块的最小值
\(T\leq 10\),\(n\leq 10^5\),\(a_i\leq 10^5\)
考场上有差不多的想法,但是不完善,暴搜数组开小只拿了 \(8\) 分
暴力来说建边和计算都是 \(\mathcal{O}(n^2)\)
考虑快速建边,定义单位合数为可以表示成两个质数乘积的合数,对每个单位合数建一个虚点,令每个点与自己的单位合数因数连边,可以快速建边。
对于计算,首先观察可发现肯定是割掉最大联通块里的一个割点,最后再计算最大联通块。割点用 Tarjan 求即可
但是 Tarjan 求割点到底写不写栈?我也不清楚,不写栈是错的,那就写栈吧
code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=4e6+10,M=1e7+10,R=1e7+10;
int T,n,a[N],mxa,ans;
int prime[R],v[R],tp,id[R],tt;
int dfn[N],low[N],cut[N],siz[N],cnt,sta[N],top;
int head[N],ver[M],nxt[M],tot=1,node;
int fa[N],wei[N];
void prework()
{
for(int i=2; i<=1e7; i++)
{
if(!v[i])
{
prime[++tp]=i;
v[i]=i;
}
for(int j=1; j<=tp; j++)
{
if(prime[j]>v[i] || prime[j]>1e7/i)
break;
v[i*prime[j]]=prime[j];
}
}
for(int i=2; i<=1e7; i++)
if(v[i]!=i && v[i/v[i]]==i/v[i])
id[i]=++tt;
}
void init()
{
tot=1; mxa=cnt=top=0;
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(cut,0,sizeof(cut));
memset(siz,0,sizeof(siz));
}
int get(int x)
{
if(x==fa[x])
return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y)
{
int fx=get(x),fy=get(y);
if(fx==fy)
return;
fa[fx]=fy; wei[fy]+=wei[fx];
}
void add(int x,int y)
{
ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
ver[++tot]=x; nxt[tot]=head[y]; head[y]=tot;
}
void add_G()
{
node=tt+n;
for(int i=1; i<=n; i++)
{
int p[15]={0},c[15]={0};
int x=a[i];
while(x>1)
{
int w=v[x];
p[++p[0]]=w;
while(x%w==0)
c[p[0]]++,x/=w;
}
for(int j=1; j<=p[0]; j++)
{
for(int k=j+(c[j]<=1); k<=p[0]; k++)
{
if(1LL*p[j]*p[k]<1e7)
{
int v=id[p[j]*p[k]]+n;
add(i,v);
merge(i,v);
}
}
}
}
}
void tarjan(int x,int root,int large)
{
dfn[x]=low[x]=++cnt;
sta[++top]=x;
if(x<=n)
siz[x]=1;
int flag=0,sum=0,res=1;
for(int i=head[x]; i; i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y,root,large);
low[x]=min(low[x],low[y]);
if(dfn[x]<=low[y])
{
flag++;
if(flag>1 || x!=root)
cut[x]=1;
}
if(dfn[x]==low[y])
{
int z,s=0;
do
{
z=sta[top--];
siz[x]+=siz[z];
s+=siz[z];
}while(z!=y);
res=max(res,s);
}
}
else
low[x]=min(low[x],dfn[y]);
}
if(cut[x])
res=max(res,large-siz[x]);
else
res=large-1;
if(x<=n)
ans=min(ans,res);
}
void mian()
{
init();
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=1; i<=4e6; i++)
fa[i]=i,wei[i]=(i<=n);
add_G();
int mx=0,rt=0,se=0;
for(int i=1; i<=node; i++)
{
if(fa[i]!=i || !wei[i])
continue;
if(wei[i]>mx)
se=mx,mx=wei[i],rt=i;
else if(wei[i]>se)
se=wei[i];
}
ans=mx;
tarjan(rt,rt,mx);
printf("%d\n",max(ans,se));
}
int main()
{
freopen("connect.in","r",stdin);
freopen("connect.out","w",stdout);
prework();
scanf("%d",&T);
while(T--)
mian();
return 0;
}
2023.10.13
(牛客场)
T1 矩阵交换
一个 \(n\times m\) 的矩阵 \(A\),\(A_{i,j}\in\{1,2,3\}\)。每次可以任意交换两行,问能否使每列单调不降
\(T,n\leq 100\)
签到题,写了 \(1.5\rm h\),纯唐
code
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N=110;
int T,n,m,a[N][N],cnt;
struct node{int id,val;}ans[N];
vector <pii> pos;
pii tmp[N];
void init()
{
memset(a,0,sizeof(a));
int len=pos.size();
for(int i=0; i<len; i++)
pos.pop_back();
}
void mian()
{
init();
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
{
ans[i].id=i;
for(int j=1; j<=m; j++)
scanf("%d",&a[i][j]);
}
pos.push_back({1,n});
for(int i=1; i<=m; i++)
{
for(int j=1; j<=n; j++)
ans[j].val=a[ans[j].id][i];
cnt=0;
for(auto v:pos)
{
sort(ans+v.first,ans+v.second+1,[](node x,node y){return x.val<y.val;});
int l=v.first,r=v.first;
for(int j=v.first+1; j<=v.second; j++)
{
r++;
if(a[ans[j].id][i]!=a[ans[j-1].id][i])
tmp[++cnt]={l,r-1},l=r;
}
tmp[++cnt]={l,r};
}
int len=pos.size();
for(int j=0; j<len; j++)
pos.pop_back();
for(int j=1; j<=cnt; j++)
pos.push_back(tmp[j]);
}
for(int i=1; i<=m; i++)
{
for(int j=2; j<=n; j++)
{
if(a[ans[j].id][i]<a[ans[j-1].id][i])
{
printf("NO\n");
return;
}
}
}
printf("YES\n");
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d",&T);
while(T--)
mian();
return 0;
}
T2 砖块摆放
原题??[ARC117C] Tricolor Pyramid
好题
这个操作很难直接做,所以考虑将操作转化成一些数字上的操作
一个牛逼的转化是令 \(A,B,C\) 分别等于 \(0,1,2\),每次操作即 \(2(x+y)\bmod 3\)
而我在考场上贺了 SError 的做法,令 \(A,B,C\) 分别等于 \(1,2,4\),每次操作即求 \((xy)^{-1}\pmod 7\)
然后通过枚举与打表可以发现,对于第 \(i\) 个砖块来说,对顶上的贡献即 \(a_i^{\binom{n-1}{i-1}}\) 如果 \(n\) 是偶数的话还要再求一次逆元
记 \(x=\dbinom{n-1}{i-1}\),因为 \(x\) 很大,所以欧拉降幂得 \(a_i^{x\bmod \varphi(7)}=a_i^{x\bmod 6}\)。所以即求 \(\dbinom{n-1}{i-1} \bmod 6\)。由于 \(6\) 不是质数,所以不能直接用 \(\rm Lucas\) 定理,但是我们可以先对 \(2\) 和 \(3\) 用 \(\rm Lucas\) 定理,再用中国剩余定理求出来
代码不难写
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e5+10,MOD=7;
int T,n,a[N];
char s[N];
int c[5][5];
int ksm(int x,int y)
{
int res=1;
while(y)
{
if(y&1)
res=1LL*res*x%MOD;
x=1LL*x*x%MOD;
y>>=1;
}
return res;
}
void prework()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
c[2][0]=1; c[2][1]=2; c[2][2]=1;
c[3][0]=1; c[3][1]=c[3][2]=3; c[3][3]=1;
}
int C(int x,int y,int p)
{
return c[x][y];
}
int lucas(int x,int y,int p)
{
if(y==0)
return 1;
if(x<p && y<p)
return C(x%p,y%p,p);
return 1LL*C(x%p,y%p,p)*lucas(x/p,y/p,p)%p;
}
int crt(int a1,int a2)
{
int m=6,m1=2,m2=3;
int M1=3,M2=2;
int t1=ksm(M1,m1-2),t2=ksm(M2,m2-2);
int ans=1LL*(1LL*a1*M1%m*t1%m+1LL*a2*M2%m*t2%m)%m;
return ans;
}
void mian()
{
scanf("%d%s",&n,s+1);
for(int i=1; i<=n; i++)
{
if(s[i]=='A')
a[i]=1;
else if(s[i]=='B')
a[i]=2;
else
a[i]=4;
}
int ans=1;
for(int i=1; i<=n; i++)
{
int ind1=lucas(n-1,i-1,2),ind2=lucas(n-1,i-1,3),res=1;
int ind=crt(ind1,ind2);
res=ksm(a[i],ind);
if(n%2==0)
res=ksm(res,MOD-2);
ans=1LL*ans*res%MOD;
}
if(ans==1)
puts("A");
else if(ans==2)
puts("B");
else
puts("C");
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
prework();
scanf("%d",&T);
while(T--)
mian();
return 0;
}
T3 学习 LIS
有一个长度为 \(n\) 的序列 \(a\),满足 \(a_i\in [1,m]\) 且 \(a_i\in \mathbb{N^*}\)。对 \(a\) 求最长上升子序列,记 \(f_i\) 为以 \(i\) 结尾的 LIS 的长度。
现在给定 \(f\) 数组,求有多少序列 \(a\) 满足条件
\(1\leq n\leq 20,\,1\leq m\leq 3000\)
很难
首先 \(n\) 一眼状压。我们发现,我们只关注原序列到底有多少种不同的数字以及它们的相对关系,并不关注出现了什么。所以我们定义“单位序列”为其中元素是从 \(1\) 开始连续的一段的序列,如 \(\{1,2,1,3\}\),它可以用来代表 \(\{1,4,1,6\},\{1,5,1,8\}\) 等。我们只需计算“单位序列”的方案数,再乘上组合数即可
考虑计算方案,设 \(f_{i,s}\) 表示填了 \([1,i]\) 的数,填的位置集合为 \(s\) 的方案数。考虑“我为人人”,枚举新填的位置集合 \(s'\),将它们填上 \(i+1\),判断是否合法,合法则贡献上去。枚举子集转移容易做到 \(\mathcal{O}(n^23^n)\)
再把枚举子集的过程拉出来,设 \(f_{i,j,s}\) 表示填了 \([1,i-1]\) 的数,考虑从后往前数第 \(j\) 个位置填不填 \(i\),当前填了的位置集合为 \(s\)。由于我们并无法确定到底有没有使用 \(i\),所以我们求得的实际上是最多用了 \(i\) 个不同的数字的方案数,记为 \(ans_i\)
考虑容斥,则有 \(Ans_i=ans_i-\sum\limits_{j=1}^{i-1}\binom{i}{j}Ans_j\)
预处理一些东西可以做到 \(\mathcal{O}(n^22^n)\)
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=25,M=3010,SS=(1<<20)+10,MOD=998244353;
int n,m,a[N],pre[SS];
int C[M][M],f[2][N][SS],ans[N];
void prework()
{
for(int i=0; i<=m; i++)
C[i][0]=1;
for(int i=1; i<=m; i++)
for(int j=1; j<=i; j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
int S=(1<<n)-1;
for(int s=0; s<=S; s++)
{
for(int i=1; i<=n; i++)
if(s&(1<<i-1))
pre[s]=max(pre[s],a[i]);
}
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
prework();
int S=(1<<n)-1,cur=1,tur=0;
f[cur][n][0]=1;
for(int t=1; t<=n; t++)
{
for(int i=n; i>=1; i--)
{
for(int s=0; s<=S; s++)
{
if(f[cur][i][s])
{
int tmp=f[cur][i][s];
if(i>1)
(f[cur][i-1][s]+=tmp)%=MOD;
else
(f[tur][n][s]+=tmp)%=MOD;
if(!(s&(1<<i-1)) && pre[s&((1<<i-1)-1)]+1==a[i])
{
if(i>1)
(f[cur][i-1][s|(1<<i-1)]+=tmp)%=MOD;
else
(f[tur][n][s|(1<<i-1)]+=tmp)%=MOD;
}
f[cur][i][s]=0;
}
}
}
ans[t]=f[tur][n][S];
swap(cur,tur);
}
int sum=0;
for(int i=1; i<=n; i++)
{
for(int j=i-1; j>=1; j--)
(ans[i]+=(1LL*(-1)*C[i][j]*ans[j]%MOD+MOD))%=MOD;
(sum+=1LL*C[m][i]*ans[i]%MOD)%=MOD;
}
printf("%d",sum);
return 0;
}
2023.10.16
T1 智乃的差分
2023.10.17
计数场
T1 序列计数
有 \(n\) 头奶牛,每头奶牛的品种 \(a_i\in\{'A'\sim 'J'\}\),现在想要从中选若干头奶牛出来拍照(不改变顺序)
定义一种照片的混乱度为照片中相邻但种类不同的奶牛的对数。若将一张照片中的所有奶牛任意改变顺序,而原图的混乱度仍是最小(或之一)的话,则称这张照片是“好的”,求共能拍出多少张“好的照片
\(T\leq 5,\,n\leq 1000\)
签到题,只拿了 60 分……
一开始设 \(f_{i,s}\) 表示以第 \(i\) 头奶牛为结尾,选的品种集合为 \(s\) 的方案数,转移是 \(\mathcal{O}(n)\) 的,总复杂度 \(\mathcal{O}(n^22^{10})\)
果然还是状态设的不够好,设 \(f_{i,s,k}\) 表示考虑到第 \(i\) 位,选的品种集合为 \(s\),最后一位颜色为 \(k\) 的方案数
则有 \(f_{i,s,k}=\begin{cases}\sum\limits_{j\ne a_i}f_{i-1,s',j}+2\times f_{i-1,s,k}&a_i=k\\f_{i-1,s,k}&a_i\ne k\end{cases}\)
滚个前缀和优化即可,空间很小要用滚动数组
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1010,M=(1<<10)+10,MOD=998244353;
bool Mbe;
int T,n,a[N],tot,t[N][15],f[2][M][15],g[2][M];
char s[N];
map <char,int> id;
void init()
{
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
tot=0;
id.clear();
}
void mian()
{
init();
scanf("%d%s",&n,s+1);
for(int i=1; i<=n; i++)
{
if(id.find(s[i])==id.end())
{
id[s[i]]=++tot;
a[i]=tot;
}
else
a[i]=id[s[i]];
}
f[0][0][0]=g[0][0]=1; int S=(1<<tot)-1;
for(int i=1; i<=n; i++)
{
for(int s=0; s<=S; s++)
{
g[i&1][s]=0;
for(int k=1; k<=tot; k++)
{
f[i&1][s][k]=0;
if(!(s&(1<<k-1)))
continue;
if(a[i]==k)
(f[i&1][s][k]+=g[(i&1)^1][s^(1<<k-1)]+f[(i&1)^1][s][k])%=MOD;
(f[i&1][s][k]+=f[(i&1)^1][s][k])%=MOD;
(g[i&1][s]+=f[i&1][s][k])%=MOD;
}
// cout<<i<<" "<<s<<" "<<g[i][s]<<endl;
}
f[i&1][0][0]=g[i&1][0]=1;
}
int ans=0;
for(int s=1; s<=S; s++)
(ans+=g[n&1][s])%=MOD;
// int ans=0;
// for(int s=1; s<=S; s++)
// (ans+=f[s][n])%=MOD;//,cout<<s<<" "<<f[s][n]<<endl;
printf("%d\n",ans);
}
bool Med;
int main()
{
freopen("counta.in","r",stdin);
freopen("counta.out","w",stdout);
// fprintf(stderr,"%.3lfMB\n",(&Mbe-&Med)/1048576.0);
scanf("%d",&T);
while(T--)
mian();
return 0;
}
T2 子段计数
CF1736C2 Good Subarrays (Hard Version)
再也不写线段树二分了!!!!!!
设 \(a_l\) 表示以 \(l\) 为左端点最远能到达的点,则有 \(\max\limits_{i=l}^{a_l}\{t_i-i\}+l>0\),移项得 \(l>i-t_i\),这东西可以 \(\mathcal{O}(n\log^2 n)\) 二分预处理出来,所以初始的答案可以计算出来,那我们只需考虑 \(\Delta ans\)
对于操作 \((x,v)\),分三种情况讨论:
-
\(t_x=v\):此时 \(\Delta =0\)
-
\(t_x>v\):这时会有一段区间越不过 \(x\) 了,将对应贡献减去即可,是简单的
-
\(t_x<v\):此时会有原先一段越不过 \(x\) 的区间越过了 \(x\)。考虑预处理一个数组 \(b_l\) 表示在忽略 \(a_l+1\) 的情况下以 \(l\) 为左端点最远能到达哪里,这也是好预处理的。\(\Delta\) 即 \(\sum b-\sum a\)
显然你用兔队线段树也可以
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e5+10,INF=1e18;
int n,t[N],a[N],b[N],m;
LL ans,sum[N],sum2[N];
#define lc(p) p<<1
#define rc(p) p<<1|1
struct SegmentTree
{
LL mx;
#define mx(x) tree[x].mx
}tree[N<<2];
void pushup(int p)
{
mx(p)=max(mx(lc(p)),mx(rc(p)));
}
void build(int p,int l,int r)
{
mx(p)=-INF;
if(l==r)
{
mx(p)=(LL)a[l];
return;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
pushup(p);
}
LL ask(int p,int l,int r,int ql,int qr)
{
if(ql<=l && qr>=r)
return mx(p);
int mid=(l+r)>>1;
LL res=-INF;
if(ql<=mid)
res=max(res,ask(lc(p),l,mid,ql,qr));
if(qr>mid)
res=max(res,ask(rc(p),mid+1,r,ql,qr));
return res;
}
int find(int l,int r,int v)
{
int lo=l,hi=r,res=lo-1;
while(lo<=hi)
{
int mid=(lo+hi)>>1;
if(ask(1,1,n,lo,mid)<v)
lo=mid+1,res=mid;
else
hi=mid-1;
}
return res;
}
int find2(int l,int r,int v)
{
int lo=l-1,hi=r+1;
while(lo+1<hi)
{
int mid=(lo+hi)>>1;
if(a[mid]>=v)
hi=mid;
else
lo=mid;
}
if(a[lo]>=v && lo!=0)
return lo;
return hi;
}
LL get(int l,int r)
{
LL res1=1LL*l*(l-1)/2;
LL res2=1LL*(1+r)*r/2;
return res2-res1;
}
int main()
{
// freopen("countb4.in","r",stdin);
// freopen("countb.out","w",stdout);
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&t[i]),a[i]=i-t[i];
build(1,1,n);
for(int i=1; i<=n; i++)
{
a[i]=find(i,n,i);
ans+=1LL*(a[i]-i+1);
sum[i]=sum[i-1]+1LL*(a[i]-i+1);
if(a[i]==n)
b[i]=n;
else if(a[i]==n-1)
b[i]=n;
else
{
b[i]=find(a[i]+2,n,i);
if(b[i]>n)
b[i]=n;
}
sum2[i]=sum2[i-1]+1LL*(b[i]-i+1);
}
scanf("%d",&m);
while(m--)
{
int x,v;
scanf("%d%d",&x,&v);
if(t[x]==v)
{
printf("%lld\n",ans);
continue;
}
int pos=find2(1,x,x),l=max(x-v,0);
if(t[x]>v)
{
if(l<pos)
{
printf("%lld\n",ans);
continue;
}
LL pre=sum[l]-sum[pos-1];
LL cur=1LL*(l-pos+1)*x-get(pos,l);
LL res=ans-pre+cur;
printf("%lld\n",res);
}
else
{
int pos2=find2(1,x-1,x-1);
if(pos2<=l)
pos2=l+1;
LL res=ans-sum[pos-1]+sum[pos2-1];
res=res+sum2[pos-1]-sum2[pos2-1];
printf("%lld\n",res);
}
}
return 0;
}
T3 组合计数
CF1307E Cow and Treats 的加强版,\(n,m\leq 10^5\)
先讨论弱化版
预处理 \(L_i,R_i\) 表示第 \(i\) 头奶牛从左/从右走会在哪个草停下,若不会停下即吃不饱则不考虑
考虑枚举第一头从左往右走的奶牛 \(p\),它将会在 \(L_p\) 处停下,此时草被划分成 \([1,L_p)\) 和 \((L_p,n]\) 两个区间,后面的奶牛都不能越过断点
显然一种颜色的奶牛最多只能有 \(2\) 头。又因为具体的顺序无关紧要,所以我们可以分不同颜色来考虑,对每种颜色的奶牛计算三个数 \(s,sl,sr\),分别表示
-
\(s\):\(L_x<L_p,R_x>R_p\) 的个数
-
\(sl\):\(L_x<L_p,R_x\leq R_p\) 的个数
-
\(sr\):\(L_x\geq L_p,R_x>R_p\) 的个数
容易发现 \(sl,sr\) 不能同时大于 \(0\)
对于 \(f_x\ne f_p\) 的情况来说:
-
若 \(s\ge 2\) 或者 \(s=1\) 且 \(sl,sr\) 不都为 \(0\),则可以有两头奶牛,方案数为 \(s\times (s-1+sl+sr)\)
-
否则,若 \(s,sl,sr\) 不都为 \(0\),则可以有一头奶牛,方案数为 \(2\times s+sl+sr\)
对于 \(f_x=f_p\) 的情况来说:
显然 \(x\) 只能从右边走,若 \(s,sr\) 不都为 \(0\),则可以有一头奶牛,方案数为 \(s+sr\)
时间复杂度 \(\mathcal{O}(m^2)\),可以通过弱化版
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=5010,MOD=1e9+7;
int n,m,c[N],f[N],h[N],L[N],R[N],sum[N];
vector <int> col[N],cow[N];
int calc(int x,int p)
{
int cnt=(x>0),res=1;
for(int i=1; i<=n; i++)
{
int s=0,sl=0,sr=0;
for(int j:cow[i])
{
if(j==x)
continue;
if(L[j]<p && R[j]>p)
s++;
else if(L[j]<p)
sl++;
else if(R[j]>p)
sr++;
}
if(f[x]==i)
{
if(s+sr>=1)
cnt++,res=1LL*res*(s+sr)%MOD;
}
else if((s>=2) || (s==1 && sl+sr>=1))
cnt+=2,res=1LL*res*s%MOD*(s-1+sl+sr)%MOD;
else if(s+sl+sr>=1)
cnt++,res=1LL*res*(2*s+sl+sr)%MOD;
}
(sum[cnt]+=res)%=MOD;
return cnt;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&c[i]),col[c[i]].push_back(i);
for(int i=1; i<=m; i++)
{
scanf("%d%d",&f[i],&h[i]);
if(h[i]>col[f[i]].size())
continue;
L[i]=col[f[i]][h[i]-1];
R[i]=col[f[i]][col[f[i]].size()-h[i]];
cow[f[i]].push_back(i);
}
int ans=calc(0,0);
for(int i=1; i<=n; i++)
for(int j:cow[i])
ans=max(ans,calc(j,L[j]));
printf("%d %d",ans,sum[ans]);
return 0;
}
再来考虑强化版
我们发现每次枚举 \(p\) 会有大量重复计算,考虑对于每个 \(l_p=i\) 的 \(p\) 只枚举一次,也就是说,对于每个草我们都只枚举一次
我们可以先让所有奶牛从右边走,可以计算出此时的奶牛数和方案数。之后,将断点右移,对于 \(f_x=c_i\) 的奶牛 \(x\),重新计算贡献。在下次移动断点前,将每一头奶牛可以走的方向更新一下
具体来说,设 \(dir_i=0/1/2/3\) 表示第 \(i\) 头奶牛的方向,\(s_{x,d}\) 表示第 \(x\) 种颜色的奶牛中方向为 \(d\) 的个数,初始化 \(dir_i=2,s_{f_i,2}++\)。再对于每种颜色分别计算,可以算出初始的答案,记 \(val_i\) 为第 \(i\) 种颜色的奶牛最优情况下的方案数
之后,从左到右枚举断点,先将当前颜色的贡献减去,再重新计算
最后,将 \(L_x=i\) 和 \(R_x=i+1\) 的奶牛的方向更新一下
时间复杂度 \(\mathcal{O}(n\log P)\),瓶颈在于求逆元,可以扫两遍做到 \(\mathcal{O}(n)\),但没必要
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e5+10,MOD=1e9+7;
int T,n,m,c[N],f[N],h[N],L[N],R[N];
int cnt,cur,s[N][5],dir[N],val[N],sum[N],ans;
vector <int> col[N],posl[N],posr[N];
bool vis[N];
void init()
{
for(int i=1; i<=n; i++)
posl[i].clear(),posr[i].clear(),col[i].clear();
memset(vis,0,sizeof(vis));
memset(s,0,sizeof(s));
memset(sum,0,sizeof(sum));
}
int ksm(int x,int y)
{
int res=1;
while(y)
{
if(y&1)
res=1LL*res*x%MOD;
x=1LL*x*x%MOD;
y>>=1;
}
return res;
}
int get(int x)
{
if(s[x][3]>=2 || (s[x][3]==1 && s[x][1]+s[x][2]>=1))
return 2;
if(s[x][3]+s[x][2]+s[x][1]>=1)
return 1;
return 0;
}
void add(int x,int y)
{
int cc=f[x],tmp=get(cc),res=1;
cnt+=y*tmp;
if(tmp==2)
res=1LL*s[cc][3]*(s[cc][3]-1+s[cc][2]+s[cc][1])%MOD;
else if(tmp==1)
res=1LL*(2*s[cc][3]+s[cc][2]+s[cc][1])%MOD;
if(y==-1)
res=ksm(res,MOD-2);
val[cc]=1LL*val[cc]*res%MOD;
cur=1LL*cur*res%MOD;
}
void update(int x,int y)
{
add(x,-1); s[f[x]][dir[x]]--;
dir[x]=y;
s[f[x]][dir[x]]++; add(x,1);
}
void mian()
{
init();
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&c[i]),col[c[i]].push_back(i),val[i]=1;
for(int i=1; i<=m; i++)
{
scanf("%d%d",&f[i],&h[i]);
if(h[i]>col[f[i]].size())
continue;
L[i]=col[f[i]][h[i]-1];
R[i]=col[f[i]][col[f[i]].size()-h[i]];
posl[L[i]].push_back(i);
posr[R[i]].push_back(i);
s[f[i]][dir[i]=2]++;
}
cnt=0; cur=1;
for(int i=1; i<=m; i++)
if(!vis[f[i]])
vis[f[i]]=1,add(i,1);
sum[ans=cnt]=cur;
for(int i=0; i<=n; i++)
{
int c=::c[i];
if(posl[i].size())
{
int x=posl[i][0],delta=s[c][3]+s[c][2]-(R[x]>i);
cnt-=get(c);
cur=1LL*cur*ksm(val[c],MOD-2)%MOD;
int nowcnt=cnt+1+(delta>0),deltacur=max(1,delta);
ans=max(ans,nowcnt);
(sum[nowcnt]+=1LL*cur*deltacur%MOD)%=MOD;
cnt+=get(c);
cur=1LL*cur*val[c]%MOD;
}
for(int x:posl[i])
update(x,dir[x]^1);
for(int x:posr[i+1])
update(x,dir[x]^2);
}
printf("%d %d\n",ans,sum[ans]);
}
int main()
{
freopen("countc.in","r",stdin);
freopen("countc.out","w",stdout);
scanf("%d",&T);
while(T--)
mian();
return 0;
}

浙公网安备 33010602011771号