省选联测 (7~13)
省选联测7 水题

做过的第二道将询问分块的题。
考虑将询问分块后,处理每个查询时扫一遍前面的修改对查询造成的影响。发现可以将链分成两部分,一部分是被修改过的,一部分没有,设分界点为 \(mxd\),发现 \(mxd\) 为查询节点与修改节点 \(lca\) 深度最大值。\(lca\) 可以预先求出整个块的。
设处理的节点为 \(x\),我们维护几个东西 \(mx_{col}\) 表示颜色 \(col\) 出现的最深深度,\(dep_{i}\) 表示这个链深度为 \(i\) 的位置的颜色。对于没修改过的,我们只需要找到 \(mx_{col}\) 在 \([mxd,d_x]\) 之间的就可以。我们可以值域分块,设 \(f_i\) 表示深度为 \([B*(i-1)+1,B*i]\) 的颜色有几种,这里面每个颜色只会存在与一个深度。这样就可以 \(O(1)-O(\sqrt n)\)。
对于被染色的,我们可以直接从后往前扫,扫的时候特殊处理。
对于重构的话,意识从后往前扫修改,发现修改过的不会再修改,那么就可以 \(O(n)\)。
总复杂度 \(O(n \sqrt n \log n)\),可过。
不会树分块。
code
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+10;
int c[N];
struct asd{
int op,u,c;
}a[N];
int head[N],nex[N*2],ver[N*2],tot=0;
void add(int x,int y){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int fa[N],d[N],top[N],siz[N],son[N];
void dfs1(int x,int fat){
d[x]=d[fat]+1;
siz[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fat) continue;
dfs1(y,x);
siz[x]+=siz[y];
if(siz[son[x]]<siz[y]) son[x]=y;
}
}
void dfs2(int x,int tp){
top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y!=fa[x] && y!=son[x]){
dfs2(y,y);
}
}
}
int ask_lca(int x,int y){
int fx=top[x],fy=top[y];
while(fx!=fy){
if(d[fx]>d[fy]){
x=fa[fx],fx=top[x];
}
else{
y=fa[fy],fy=top[y];
}
}
if(d[x]<d[y]) return x;
else return y;
}
int B=900,I=900;
int ls[N],rs[N],pos[N];
vector<int> rk[N];
int dep[N],mx[N];
int f[N];
int ans[N];
int __lca[5000][5000];
bool flat[N],vis[N];
int kl[N];
void dfs(int x){
dep[d[x]]=c[x];
int mx1=mx[c[x]];
mx[c[x]]=d[x];
int t1;
if(mx1){
t1=mx1/B;
if(mx1%B) t1++;
f[t1]--;
}
int t2=d[x]/B;
if(d[x]%B) t2++;
f[t2]++;
if(rk[x].size()){
int cnt=rk[x].size();
for(int i=0;i<cnt;i++){
int op=rk[x][i];
int mxd=0;
for(int i=ls[pos[op]];i<op;i++){
if(a[i].op==1){
int lca=__lca[i-ls[pos[op]]][op-ls[pos[op]]];
mxd=max(mxd,d[lca]);
}
}
int sum=0;
for(int i=1;i<=t2;i++) sum+=f[i];
int t3=mxd/B;
if(mxd%B) t3++;
for(int i=1;i<=t3;i++) sum-=f[i];
int qwq=0;
int up=min(d[x],B*t3);
for(int i=mxd+1;i<=up;i++){
int col=dep[i];
if(mx[col]<=up && !vis[col]){
sum++;
vis[col]=1;
kl[++qwq]=col;
}
}
for(int i=1;i<=qwq;i++) vis[kl[i]]=0;
int p=0;
for(int i=op;i>=ls[pos[op]];i--){
if(a[i].op==1){
int lca=__lca[i-ls[pos[op]]][op-ls[pos[op]]];
int col=a[i].c;
if(d[lca]<=p) continue;
else{
p=d[lca];
if(mx[col]<=mxd && !vis[col]){
sum++;
vis[col]=1;
kl[++qwq]=col;
}
}
}
}
for(int i=1;i<=qwq;i++) vis[kl[i]]=0;
ans[op]=sum;
}
}
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa[x]) continue;
dfs(y);
}
if(mx1) f[t1]++;
f[t2]--;
mx[c[x]]=mx1;
dep[x]=0;
}
inline int read(){
int x(0);bool f(0);char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()) f^=ch=='-';
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f?x=-x:x;
}
inline void write(int x){
x<0?x=-x,putchar('-'):0;static short Sta[50],top(0);
do{Sta[++top]=x%10;x/=10;}while(x);
while(top) putchar(Sta[top--]|48);
putchar('\n');
}
signed main(){
freopen("simple.in","r",stdin);
freopen("simple.out","w",stdout);
int T;
T=read();
for(int kkk=1;kkk<=T;kkk++){
int n,q;
n=read(),q=read();
for(int i=2;i<=n;i++){
fa[i]=read();
add(fa[i],i);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=q;i++){
a[i].op=read();
if(a[i].op==1) a[i].u=read(),a[i].c=read();
else a[i].u=read();
}
int t=0;
for(int i=1;i*B<=q;i++){
ls[i]=B*(i-1)+1;
rs[i]=B*i;
t=i;
}
if(rs[t]<q) t++,ls[t]=rs[t-1]+1,rs[t]=q;
for(int i=1;i<=t;i++)
for(int j=ls[i];j<=rs[i];j++)
pos[j]=i;
for(int op=1;op<=t;++op){
int idx=0;
for(int i=ls[op];i<=rs[op];++i){
if(a[i].op==2) rk[a[i].u].push_back(i);
for(int j=i+1;j<=rs[op];j++){
int x=a[i].u,y=a[j].u;
__lca[i-ls[op]][j-ls[op]]=__lca[j-ls[op]][i-ls[op]]=ask_lca(x,y);
}
}
dfs(1);
int qwq=0;
for(int i=rs[op];i>=ls[op];i--){
if(a[i].op==2) rk[a[i].u].clear();
if(a[i].op==1){
int tmp=a[i].u;
int col=a[i].c;
while(tmp){
if(flat[tmp]) break;
flat[tmp]=1;
kl[++qwq]=tmp;
c[tmp]=col;
tmp=fa[tmp];
}
}
}
for(int i=1;i<=qwq;i++) flat[kl[i]]=0;
}
for(int i=1;i<=t;i++){
for(int j=ls[i];j<=rs[i];j++){
if(a[j].op==2) write(ans[j]);
}
}
for(int i=1;i<=tot;i++) head[i]=0;
memset(son,0,sizeof(int)*(n+1));
memset(d,0,sizeof(int)*(n+1));
memset(top,0,sizeof(int)*(n+1));
tot=0;
}
}
NOIP2023模拟20联测41 红楼 ~ Eastern Dream
给出一个长度为 \(n\) 的序列 \(a\),有 \(m\) 次操作,格式如下:
1 x l k 对于所有满足 \((i-1) \mod x \leq y\) 的 \(i\),将 \(a_i\) 的值加 \(k\)。
2 l r 求 \(\sum_{i-l}^{r} a_i\)
数组下标从 \(1\) 开始。
题解:
考虑根号分治,对于 \(x \leq B\) 的我们发现,虽然块数多,但是循环节少,所以我们维护 \(f_{i,j}\) 表示 \(\mod i\) 第 \(j\) 位上的增量,然后我们求一个前缀和 \(sum_{i,j}=\sum_{i=1}^{j} f_{x,j}\),对于查询这部分时,可以枚举循环节,\(O(1)\) 计算,那么这部分是 \(O(\sqrt n)-O(\sqrt n)\)。
对于 \(x > B\),这种情况块数很少,我们可以考虑对每个块差分,发现直接暴跳复杂度也就 \(O(\sqrt n)\)。对于查询可以将操作分块,先算上之前块的贡献,然后暴力扫这个块内的修改对这个查询的影响,可以 \(O(1)\) 计算,复杂度也是 \(O(\sqrt n)-O(\sqrt n)\)。
对于重构,直接做两次前缀和更新就可以。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+10;
int a[N];
struct asd{
int op,x,y;
int k;
int l,r;
}b[N];
long long f[700][700];
long long sum[700][700];
long long qz[N];
long long ans[N];
int pos[N],l[N],r[N];
long long c[N],sum1[N],d[N],g[N];
inline int read(){
int x(0);char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()){}
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x;
}
inline void write(int x){
x<0?x=-x,putchar('-'):0;static short Sta[50],top(0);
do{Sta[++top]=x%10;x/=10;}while(x);
while(top) putchar(Sta[top--]|48);
putchar('\n');
}
signed main(){
freopen("scarlet.in","r",stdin);
freopen("scarlet.out","w",stdout);
int n,m;
n=read(),m=read();
for(int i=1;i<=n;++i){
a[i]=read();
qz[i]=qz[i-1]+a[i];
}
int B=sqrt(n);
int t=sqrt(m);
for(int i=1;i<=t;++i){
l[i]=(i-1)*t+1;
r[i]=i*t;
}
if(r[t]<m) t++,l[t]=r[t-1]+1,r[t]=m;
for(int i=1;i<=t;++i)
for(int j=l[i];j<=r[i];++j)
pos[j]=i;
for(int p=1;p<=m;++p){
b[p].op=read();
if(b[p].op==1){
b[p].x=read(),b[p].y=read(),b[p].k=read();
if(b[p].x<=B){
int lim=min(b[p].x-1,b[p].y);
for(int i=0;i<=lim;i++) f[b[p].x][i]+=b[p].k;
sum[b[p].x][0]=f[b[p].x][0];
for(int i=1;i<=b[p].x-1;i++) sum[b[p].x][i]=sum[b[p].x][i-1]+f[b[p].x][i];
}
else{
for(int i=1;i<=n;i+=b[p].x){
c[i]+=b[p].k;
if(i+b[p].y+1>n) break;
c[i+b[p].y+1]-=b[p].k;
}
}
}
else{
b[p].l=read(),b[p].r=read();
for(int i=1;i<=B;++i){
long long wr=sum[i][(b[p].r-1)%i]+(b[p].r-1)/i*sum[i][i-1];
int ls=b[p].l-1;
long long wl=sum[i][(ls-1)%i]+(ls-1)/i*sum[i][i-1];
ans[p]+=(wr-wl);
}
ans[p]+=(sum1[b[p].r]-sum1[b[p].l-1]);
for(int i=r[pos[p]-1]+1;i<p;++i){
if(b[i].op==1){
if(b[i].x>B){
long long w=1ll*(min(b[i].x-1,b[i].y)+1)*b[i].k;
long long wr=1ll*min((b[p].r-1)%b[i].x+1,b[i].y+1)*b[i].k+(b[p].r-1)/b[i].x*w;
int ls=b[p].l-1;
long long wl=1ll*(min((ls-1)%b[i].x,b[i].y)+1)*b[i].k+(ls-1)/b[i].x*w;
ans[p]+=(wr-wl);
}
}
}
}
if(r[pos[p]]==p){
for(int i=1;i<=n;++i){
c[i]=c[i-1]+c[i];
d[i]=d[i]+c[i];
sum1[i]=sum1[i-1]+d[i];
}
memset(c,0,sizeof(long long)*(n+1));
}
if(b[p].op==2) printf("%lld\n",ans[p]+qz[b[p].r]-qz[b[p].l-1]);
}
}
省选联测9 战争模拟器

首先考虑暴力 \(dp\) 另 \(f_{l,r,k}\) 表示 \([l,r]\) 最大值为 \(k\) 时的答案, \(g_{l,r,k}\) 表示 \(f_{l,r,k}\) 的前缀最大值,那么有转移:
复杂度 $ O(n^2 \sum K_i)$
事实上我们发现并不需要保证最大值为 \(k\),因为 \(p\) 如果不是最大值一定不是优,所以只需要
我们只需要求 \(V_{p, k} \times \sum\limits_{l \le i \le p} \sum\limits_{p \le i \le r} Q_{i, j} - C_{p, k}\) 的值,我们另 \(x:V\) , \(y:C\) , \(k:Q\) , \(b\) 为所求,那么如果给 \(V\) 排个序,那么 \(x\) 单增,就可以用斜率优化,每回只需要二分凸包。
最后考虑在上面的基础上考虑改变 \(l,r,p\) 的枚举顺序,就可以使斜率单增,那么就不用二分直接扫一遍就可以。
复杂度 \(O(n^3+ n \sum K_i + \sum K_i \log K_i)\)。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
const int inf=LONG_LONG_MAX-10;
int q[N][N],sum[N][N];
int K[N];
int V[N][N],C[N][N];
struct asd{
int v,c;
}b[N][N*30];
int n;
int ask(int l,int r,int p){
return sum[p][r]-sum[p][p-1]-sum[l-1][r]+sum[l-1][p-1];
}
int f[N][N];
int st[N][N*30];
int top[N];
bool amp(asd a,asd b){
return a.v<b.v;
}
int X(int i,int j){
int kl=b[i][j].v;
return kl;
}
int Y(int i,int j){
int kl=b[i][j].c;
return kl;
}
signed main(){
freopen("war.in","r",stdin);
freopen("war.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j)
scanf("%lld",&q[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+q[i][j];
}
int mx=0;
for(int i=1;i<=n;++i){
scanf("%lld",&K[i]);
mx=max(mx,K[i]);
for(int j=1;j<=K[i];++j) scanf("%lld%lld",&b[i][j].v,&b[i][j].c);
sort(b[i]+1,b[i]+K[i]+1,amp);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i<=j) f[i][j]=-inf;
else f[i][j]=0;
}
}
__int128 it=1;
for(int i=1;i<=n;i++){
int tp=0;
for(int j=1;j<=K[i];j++){
while(tp>1 && it* (Y(i,st[i][tp])-Y(i,st[i][tp-1])) * (X(i,j)-X(i,st[i][tp-1])) >= it* (Y(i,j)-Y(i,st[i][tp-1])) * (X(i,st[i][tp])-X(i,st[i][tp-1])) ) tp--;
st[i][++tp]=j;
}
top[i]=tp;
int Q=ask(i,i,i);
int pos=1;
while(pos+1<=tp && it* (Y(i,st[i][pos+1])-Y(i,st[i][pos])) <= it* Q * (X(i,st[i][pos+1])-X(i,st[i][pos]))) pos++;
f[i][i]=b[i][st[i][pos]].v*Q-b[i][st[i][pos]].c;
}
for(int l=n;l>=1;l--){
for(int p=l;p<=n;p++){
int pos=1;
for(int r=p;r<=n;r++){
int Q=ask(l,r,p);
while(pos+1<=top[p] && it* (Y(p,st[p][pos+1])-Y(p,st[p][pos])) <= it* Q * (X(p,st[p][pos+1])-X(p,st[p][pos])) ) pos++;
f[l][r]=max(f[l][r],f[l][p-1]+f[p+1][r]+b[p][st[p][pos]].v*Q-b[p][st[p][pos]].c);
}
}
}
printf("%lld",f[1][n]);
}
省选联测9 种树
首先求出每个点的期望深度 \(dep_i\),枚举 \(i\) 接在 \(j\) 下面。
维护前缀可以 \(O(n)\) 转移。
设 \(u<v\),枚举 \(u\) 和 \(v\) 的 \(lca\),设 \(P(i)\) 为 \(i\) 为 \(lca\) 的概率,那么答案为
对于编号在 \((x,u)\) 的点既有可能在 \(u \rightarrow x\) 也有能在 \(v \rightarrow x\)。 对于编号 \((u,v)\) 的点只有可能在 \(v \rightarrow x\),所以:
注意 \(u\) 为 \(\mathrm{lca}\) 的情况特殊处理,首先求出它的概率,化简完就是 \(\frac{a_u}{b_u}\),在乘上 \(dep_u\),就是 \(lca\) 为 \(u\) 的贡献。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
int a[N],b[N],c[N];
int dep[N];
int mgml(int x,int p){
int ans=1;
while(p){
if(p&1) ans=(ans*x)%mod;
x=(x*x)%mod;
p>>=1;
}
return ans;
}
int dlca[N];
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int n,q;
scanf("%lld%lld",&n,&q);
for(int i=1;i<n;i++) scanf("%lld",&a[i]),b[i]=b[i-1]+a[i];
for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
int sum=0;
for(int i=1;i<=n;i++){
dep[i]=(sum*mgml(b[i-1],mod-2)%mod+c[i])%mod;
if(i==1) dep[i]=0;
sum=(sum+a[i]*(dep[i]+c[i])%mod)%mod;
}
for(int u=2;u<=n;u++){
dlca[u]=dlca[u-1]*b[u-2]%mod*mgml(b[u],mod-2)%mod;
dlca[u]=dlca[u]*((2*a[u-1]%mod+b[u-2])%mod)%mod*mgml(b[u-2],mod-2)%mod;
int x=u-1;
int px=a[x]*a[x]%mod*mgml(b[u-1]*b[u]%mod,mod-2)%mod;
dlca[u]=(dlca[u]+px*dep[x]%mod)%mod;
}
for(int u=1;u<=n;u++){
int op=a[u]*mgml(b[u],mod-2)%mod;
dlca[u]=(dlca[u]+op*dep[u]%mod)%mod;
}
for(int i=1;i<=q;i++){
int u,v;
scanf("%lld%lld",&u,&v);
if(u==v){
printf("0\n");
continue;
}
if(u==1){
printf("%lld\n",dep[v]);
continue;
}
if(u>v) swap(u,v);
int ans=(dep[u]+dep[v]-2*dlca[u]%mod+2*mod)%mod;
printf("%lld\n",ans);
}
}
省选联测11 Giao 徽的烤鸭

树形 \(\mathrm{DP}\),设 \(f_{u,i}\) 表示在以 \(u\) 为根的书中小于等于 \(i\) 的点全选的最大净利润。对于父子点 \(u,v\),用 \(f_{v,j}\) 更新 \(f_{u,i}\),考虑有什么限制。首先是 \(u \rightarrow v\) 的,有 \(j+1 \geq i\),然后是 \(v \rightarrow u\),有 \(i \geq j-1\)。
所以转移为 \(f_{u,i}=min(f_{v,i-1},f_{v,i},f_{v,i+1})\)。
code
// ubsan: undefined
// accoders
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005;
int w[N],v[N];
int head[N],nex[N*2],ver[N*2],tot=0;
void add(int x,int y){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int f[N][N];
int n;
int siz[N];
void dfs(int x,int fa){
f[x][0]=0;
for(int i=1;i<=n;i++){
f[x][i]=v[i-1]-w[x];
}
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa) continue;
dfs(y,x);
}
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa) continue;
f[x][0]+=max(f[y][1],f[y][0]);
for(int j=1;j<=n;j++) f[x][j]+=max({f[y][j],f[y][j-1],f[y][j+1]});
}
// for(int i=n-1;i>=1;i--) f[x][i]=max(f[x][i+1],f[x][i]);
}
signed main(){
freopen("duck.in","r",stdin);
freopen("duck.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
for(int i=0;i<n;i++) scanf("%lld",&v[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%lld%lld",&x,&y);
if(y==0) cerr<<"fk ";
add(x,y),add(y,x);
}
dfs(1,0);
int ans=0;
for(int i=0;i<=n;i++) ans=max(ans,f[1][i]);
printf("%lld",ans);
}
省选联测11 GA Dance of Fire and Ice

首先根据原根可以讲乘法转化为加法,因为原根有这样的性质:
假如 \(g\) 是 \(p\) 的原根,那么 \(g^i \equiv x (\mod p)\) 对于 \(i \in [i,p-1]\) 有 \(x \in [i,p-1]\) 一一对应。
所以将 \(x\) 变为 \(g^{\log_g x}\),那么两个数的乘法就变成了两个 \(log\) 的加法,而且还是一一对应的,然后可以用 \(\mathrm{bitset}\) 优化背包,然后对于同一个 \(p\)同时操作次数次,但是加入一次操作后没有变化,那么就可以停止,复杂度 \(p^2\),可以卡过。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6;
const int M=2*1e5+10;
int a[N],b[N];
int top1=0,top2=0;
int phi[N],prime[N];
bool nprime[N];
int p,n,g;
int lg[N];
bitset<M> bt;
void get_prime(){
phi[1]=1;
for(int i=2;i<=p;i++){
if(!nprime[i]){
prime[++prime[0]]=i;
phi[i]=i-1;
}
for(int j=1;j<=prime[0] && i*prime[j]<=p;j++){
int k=i*prime[j];
nprime[k]=1;
if(i%prime[j]==0){
phi[k]=prime[j]*phi[i];
break;
}
else phi[k]=(prime[j]-1)*phi[i];
}
}
}
int gcd(int a,int b){
if(b==0) return a;
else return gcd(b,a%b);
}
int qpow(int x,int p,int mod){
int ans=1;
while(p){
if(p&1) ans=(ans*x)%mod;
x=(x*x)%mod;
p>>=1;
}
return ans;
}
int vi[N],tp=0;
bool check(int x,int m){
if(gcd(x,m)!=1) return 0;
for(int i=1;i<=tp;i++){
// cout<<qpow(x,phi[m]/vi[i],m)<<" "<<x<<" "<<phi[m]/vi[i]<<endl;
if(qpow(x,phi[m]/vi[i],m)%m==1) return 0;
}
return 1;
}
int sum[N];
signed main(){
freopen("dance.in","r",stdin);
freopen("dance.out","w",stdout);
scanf("%lld%lld",&p,&n);
nprime[1]=1;
get_prime();
// cout<<phi[p]<<endl;
int tmp=p;
for(int i=2;i*i<=tmp;i++){
if(phi[p]%i==0){
if(!nprime[i]) vi[++tp]=i;
if(phi[p]/i!=i && !nprime[phi[p]/i]) vi[++tp]=phi[p]/i;
}
}
int t=sqrt(sqrt(p))+1;
for(int i=2;;i++){
if(check(i,p)){
g=i;
break;
}
}
for(int i=1;i<=p-1;i++){
int x=qpow(g,i,p);
lg[x]=i;
}
bt[0]=1;
int vis0=0;
for(int i=1;i<=n;i++){
int op,x;
scanf("%lld%lld",&op,&x);
x%=p;
if(x==0){
vis0=1;
continue;
}
if(!op) bt[lg[x]]=1;
else sum[lg[x]]++;
}
for(int x=1;x<p;x++){
if(sum[x]){
for(int j=1;j<=sum[x];j++){
std::bitset<M> nnext = bt;
nnext=bt|(bt>>x)|(bt<<(p-x-1));
if (nnext == bt) {
break;
} else {
bt = nnext;
}
}
}
}
int ans=0;
for(int i=0;i<p-1;i++) if(bt[i]) ans++;
cout<<ans+(vis0==1)<<endl;
}
省选联测12 硬币

我们考虑这个序列是 \(2,5,10,17,16,37,50,65\),我们枚举每一位,首先第一位为 \(2\) 然后将后面带 \(2\) 因数的消掉,发现是第 \((1+2*k)\) 位,然后继续,发现是 \(5\),我们把因数 \(5\) 消掉,发现是第 \((2+5*k)\) 位,考虑证明。
首先第 \(p\) 位满足 \(p*p+1 \mod x =0\),那么 \((p+x*k)^2+1\) 拆开后发现也可以被 \(x\) 整除。
现在考虑枚举每个数时,剩下的数都是质数。
假如枚举到一个合数 \(x\) 有一个质因数 \(p\),那么 \(x-kp > 0\) 的最小的包含 \(p\) 的因数的那个数一定可以吧当前位置的 \(p\) 消掉,所以不会剩下两个质数情况。
code
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+100;
int ans[N];
int w[N];
signed main(){
freopen("coins.in","r",stdin);
freopen("coins.out","w",stdout);
for(int i=1;i<=1e6;i++) ans[i]=w[i]=i*i+1;
for(int i=1;i<=1e6;i++){
if(w[i]==1) continue;
int k=w[i];
for(int j=i;j<=1e6;j+=k){
while(w[j]%k==0) w[j]/=k;
ans[j]=min(ans[j],k);
}
// cout<<i<<endl;
}
int q;
scanf("%lld",&q);
for(int i=1;i<=q;i++){
int x;
scanf("%lld",&x);
if(ans[x]==x*x+1){
printf("-1\n");
}
else{
printf("%lld %lld\n",ans[x],(x*x+1)/ans[x]);
}
}
}
省选联测12 猜数

首先有转移 \(f_{l,r}=\min_{l \leq p \leq r}( \max(f_{l,p-1},f_{p+1,r})+p)\)。
直接转移发现是 \(n^3\) 。
但是发现对于区间 \((l,r)\) 设关键点 \(k_0\),对于 \(p \in (l,k_0)\), \(f_{p+1,r} \leq f_{l,p-1}\) ,右侧相反。
那么我们可以先枚举 \(r\) 在枚举 \(l\),对于左侧进行单调队列, 右侧直接取决策点就可以,因为在往右均增大。复杂度 \(n^2\)。
打表发现,决策点在距离右侧 \(\frac{n}{log_{10}^{n}}\) 的范围内,所以可以滚动,复杂度 \(O(\frac{n^2}{log_{10}^{n}})\)。
还可以打表,讲序列差分后压成 \(80\) 进制后,每个数只需要占 \(3\) 个字符,进行差分后就只占 \(1\) 个字符。
compress
#include <bits/stdc++.h>
using namespace std;
const char s[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
int main() {
freopen("out.out", "r", stdin);
freopen("compress.out", "w", stdout);
int x;
while (scanf("%d,", &x) != EOF) {
putchar(s[x % 64]);
putchar(s[x / 64 % 64]);
putchar(s[x / 64 / 64 % 64]);
}
return 0;
}
code
#include<bits/stdc++.h>
using namespace std;
const int N=50005;
const int S=5005;
const int M=5100;
int f[50001];
int g[5500][5500];
int st[N],l,r;
signed main(){
freopen("guess.in","r",stdin);
freopen("guess.out","w",stdout);
int n;
scanf("%d",&n);
long long sum=0;
for(int j=2;j<=n;j++){
int p=j;
l=1,r=0;
int lim=max(1,j-S-1);
int mn=INT_MAX;
memset(g[j%M],0,sizeof(g[j%M]));
for(int i=j-1;i>=lim;i--){
while(p>i && g[j%M][(p+1)%M]<g[(p-1)%M][i%M]) p--;
while(st[l]>p && l<=r) l++;
while(l<=r && st[r]+g[j%M][(st[r]+1)%M] > i+g[j%M][(i+1)%M]) r--;
st[++r]=i;
g[j%M][i%M]=min(st[l]+g[j%M][(st[l]+1)%M],p+1+g[p%M][i%M]);
mn=min(mn,max(g[j%M][(i+1)%M],f[i-1])+i);
}
f[j]=mn;
sum+=f[j];
}
printf("%lld",sum);
}
省选联测12 猜数
设 \(A=(c_1,c_2,……,c_n)\) 表示每种牌的数量, \(f(A)\) 是状态 \(A\) 的答案,肯定可以贪心求出来,策略是能换就换。
然后发现 \(2^nn!\) 张 \(1\) 可以换成一张 \(1\)。
不妨将每种状态都倒换成 \(1\) 号卡牌,有初始状态 \(P=\sum_{i=1}^{n} a_i 2^{i-1} (i-1)!\)。
这样每个状态都对应一个数,我们设 \(a_i\) 表示第 \(i\) 包 卡牌全部倒换成 \(1\) 号卡牌的数量,\(Q\) 表示终止状态,那么有 :
最后减去 \(y \times (2_nn!-1)\) 是因为终止一定可以变为 \(2_nn!\) 以内的状态。
裴蜀定理:设 \(a,b\) 为不都为 \(0\) 的整数,则 \(ax+by=d\) 有解的充要条件是 \(d \mid \gcd(a,b)\)。
那么推广到多个数,设 \(d=\gcd(a_1,a_2,...,a_n,2^nn!-1)\),那么有 \(d \mid (Q-P)\),那么 \(Q=k*d+P%d\),我们要 \(Q\) 最小。
考虑怎么做:
方法一:暴力枚举 \(k\),然后每次都计算答案,那么复杂度 \(O(\frac{2^nn!}{d} \times n)\)。
方法二:同余最短路,就是相当于 「至少要拼几次才能拼出模 \(𝐾\) 余 \(𝑝\) 的数」,那么枚举要拿哪张牌直接对 \(x \rightarrow (x+f_i)%d\) 连边,边权为一,那么答案就是 \(dis_{P \mod d}\)。复杂度 \(O(nd)\)。
然后根号分治,对于 \(d\) 小于 \(\sqrt{2^nn!-1}\) 的执行方法二,反之方法一。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
const int M=2*1e6;
int a[N];
int n,m;
int f[N];
int g[M];
void init(){
f[0]=1;
for(int i=1;i<=n;i++) f[i]=f[i-1]*i*2;
}
int ask(){
int ans=0;
for(int i=1;i<=n;i++) ans+=a[i]*f[i-1];
return ans;
}
int query(int x){
int ans=0;
for(int i=1;i<=n;i++){
ans+=(x%(2*i));
x/=(2*i);
}
return ans;
}
void solve(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
init();
int p=ask();
int d=f[n]-1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++) scanf("%lld",&a[j]);
int sa=ask();
d=__gcd(d,sa);
}
if(d<=sqrt(f[n])){
queue<int> q;
memset(g,-1,sizeof(g));
for(int i=0;i<n;i++){
q.push(f[i]%d);
g[f[i]%d]=1;
}
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<n;i++){
int v=(x+f[i])%d;
if(g[v]==-1){
g[v]=g[x]+1;
q.push(v);
}
}
}
printf("%lld\n",min(g[p%d],query(p)));
}
else{
int ans=query(p);
for(int x=(p%d ? p%d : d);x<f[n];x+=d){
ans=min(ans,query(x));
}
printf("%lld\n",ans);
}
}
signed main(){
freopen("card.in","r",stdin);
freopen("card.out","w",stdout);
solve();
}
省选联测13 树上横跳

楼房重建的思想,首先倍增预处理出来区间最小值和区间答案,那么考虑将上下两个区间合并,首先上区间直接算入答案就可以,设上区间最小值为 \(lim\),那么考虑下区间,加入下区间最小值大于 \(lim\),那么直接用上区间最小值跳下区间就可以;反之我们将下区间分成 \(A1\) 和 \(A2\) 两个区间,如果 \(A1\) 最小值大于 \(lim\) 那么递归考虑 \(A2\);否则递归考虑 \(A1\)。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3*1e5+10;
int n,m,t;
int head[N],nex[N*2],ver[N*2],edge[N*2],tot=0;
void add(int x,int y,int w){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=w;
}
struct T2{
int dfn[N],num=0,fa[N],siz[N];
void dfs(int x,int fat){
fa[x]=fat;
dfn[x]=++num;
siz[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fat) continue;
dfs(y,x);
siz[x]+=siz[y];
}
}
int mp[3005][3005];
int anss[3005][3004];
int st[N],top,dist[N],rk[N];
int f[N][50],g[N][50];
void dfs1(int x,int fat){
st[++top]=x;
rk[x]=top;
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fat) continue;
dist[top+1]=dist[top]+edge[i];
dfs1(y,x);
}
}
int cnt[N],tp=0;
void init(){
dfs(1,0);
dfs1(1,0);
int op=top;
for(int i=1;i<=n;i++){
while(tp>0 && st[cnt[tp]]>st[i]){
f[cnt[tp]][0]=i;
tp--;
}
cnt[++tp]=i;
}
while(tp){
f[cnt[tp]][0]=n;
tp--;
}
for(int i=1;i<=n;i++) g[i][0]=(dist[f[i][0]]-dist[i])*st[i];
int t=log2(n)+1;
for(int j=1;j<=t;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1];
}
}
}
void solve(){
for(int p=1;p<=m;p++){
int x,y;
scanf("%lld%lld",&x,&y);
if(dfn[y]>=dfn[x] && dfn[y]<=dfn[x]+siz[x]-1){
int fx=rk[x],fy=rk[y];
int ans=0;
for(int i=t;i>=0;i--){
if(f[fx][i]<=fy){
ans+=g[fx][i];
fx=f[fx][i];
}
}
ans+=st[fx]*(dist[fy]-dist[fx]);
printf("%lld\n",ans);
}
else printf("-1\n");
}
}
}T;
int dep[N],dist[N],fa[N][50],mn[N][50],f[N][50];
int dfn[N],num=0,siz[N];
int merge(int lim,int x,int k){
if(mn[x][k]>=lim) return (dist[x]-dist[fa[x][k]])*lim;
if(fa[x][k]<lim) return f[x][k];
if(mn[fa[x][k-1]][k-1]>=lim) return lim*(dist[fa[x][k-1]]-dist[fa[x][k]])+merge(lim,x,k-1);
else return f[x][k]-f[fa[x][k-1]][k-1]+merge(lim,fa[x][k-1],k-1);
}
void dfs(int x,int fat){
dfn[x]=++num;
siz[x]=1;
dep[x]=dep[fat]+1;
fa[x][0]=mn[x][0]=fat;
for(int j=1;j<=t;j++){
int i=x;
fa[i][j]=fa[fa[i][j-1]][j-1];
mn[i][j]=min(mn[i][j-1],mn[fa[i][j-1]][j-1]);
f[i][j]=f[fa[i][j-1]][j-1]+merge(mn[fa[i][j-1]][j-1],i,j-1);
}
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fat) continue;
dist[y]=dist[x]+edge[i];
f[y][0]=edge[i]*x;
dfs(y,x);
siz[x]+=siz[y];
}
}
struct asd{
int x,k;
}st[N];
int solve(int x,int y){
int top=0;
int tmp=y;
for(int j=t;j>=0;j--){
if(dep[fa[tmp][j]]>=dep[x]){
st[++top]={tmp,j};
tmp=fa[tmp][j];
}
}
int ans=f[st[top].x][st[top].k];
int lim=mn[st[top].x][st[top].k];
for(int i=top-1;i>=1;i--){
ans+=merge(lim,st[i].x,st[i].k);
lim=min(lim,mn[st[i].x][st[i].k]);
}
return ans;
}
int du[N];
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%lld%lld",&n,&m);
t=log2(n)+1;
for(int i=1;i<n;i++){
int x,y,w;
scanf("%lld%lld%lld",&x,&y,&w);
add(x,y,w),add(y,x,w);
du[x]++,du[y]++;
}
int op=0;
for(int i=1;i<=n;i++){
if(du[i]>2) op=1;
}
if(!op){
T.init();
T.solve();
return 0;
}
dfs(1,0);
for(int i=1;i<=m;i++){
int x,y;
scanf("%lld%lld",&x,&y);
if(dfn[y]>=dfn[x] && dfn[y]<=dfn[x]+siz[x]-1){
printf("%lld\n",solve(x,y));
}
else printf("-1\n");
}
}
省选联测13 定价
首先考虑暴力,那么就是假如上一位有若干一,那么这一位需要找到最高位的上一位为一这一位不能为一的位(如果没有从第零位开始),然后从那一位向上找到第一位可以位一的但上一位不为一的位,然后选它并将其下面的一全部清掉。
考虑怎么优,我们维护一个栈,里面存的是我们选的那些位置的一,每一个数维护一个 \(set\) 表示那个数可以选那些一,然后再维护一个 \(bitset\) 表示为每一位有哪些数可以选一。
将上面的过程转化为我们通过 \(bitset\) 找到如果这一位选一,那么下一个不能选一的位置是哪里,将其压入优先队列,然后到达那一位的时候从优先队列中找到与这一个数有关,位数取 \(\max\)。然后这个 \(\max\) 位就是最高位的上一位为一这一位不能为一的位。然后记得将栈里元素弹出加入的时候,将优先队列里的也要删除加入,所以用可删堆。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
const int M=1000005;
const int mod=1e9+7;
bitset<N> bt[M];
set<int> s[N];
struct asd{
int op,r,c;
}a[M];
int qz[M],cnt=0;
int st[M],top=0;
unordered_map<int,int> mp;
struct qwe{
int p,k;
friend bool operator <(qwe a,qwe b){
if(a.p==b.p) return a.k<b.k;
return a.p>b.p;
}
}b[M];
priority_queue<qwe> ads,dels;
inline void insert(qwe x){
ads.push(x);
}
inline void dele(qwe x){
dels.push(x);
while(!dels.empty() && !ads.empty() & ads.top().k==dels.top().k && ads.top().p==dels.top().p){
ads.pop();
dels.pop();
}
}
inline qwe begin(){
while(!dels.empty() && !ads.empty() && ads.top().k==dels.top().k && ads.top().p==dels.top().p){
ads.pop();
dels.pop();
}
return ads.top();
}
int siz(){
if(ads.empty()) return 0;
return 1;
}
inline void clear(){
while(!dels.empty()) dels.pop();
while(!ads.empty()) ads.pop();
}
inline int qpow(int x,int p){
int ans=1;
while(p){
if(p&1) ans=(ans*x)%mod;
x=(x*x)%mod;
p>>=1;
}
return ans;
}
bool vis[M];
signed main(){
// freopen("2-1.in","r",stdin);
// freopen("price.out","w",stdout);
freopen("price.in","r",stdin);
freopen("price.out","w",stdout);
int n,m,q;
scanf("%lld%lld%lld",&n,&m,&q);
for(int i=1;i<=q;i++){
scanf("%lld",&a[i].op);
if(a[i].op==1){
scanf("%lld%lld",&a[i].r,&a[i].c);
a[i].c=m-a[i].c;
qz[++cnt]=a[i].c;
}
}
sort(qz+1,qz+cnt+1);
// cout<<cnt<<endl;
cnt=unique(qz+1,qz+cnt+1)-(qz+1);
// return 0;
for(int i=1;i<=cnt;i++){
// bt[i].set(M,1);
mp[qz[i]]=i;
b[i]={0,0};
}
for(int i=1;i<=q;i++){
if(a[i].op==1) a[i].c=mp[a[i].c];
}
for(int p=1;p<=q;p++){
if(a[p].op==1){
if(bt[a[p].c][a[p].r]){
s[a[p].r].erase(a[p].c);
bt[a[p].c][a[p].r]=0;
}
else{
s[a[p].r].insert(a[p].c);
bt[a[p].c][a[p].r]=1;
}
}
else{
top=0;
clear();
int getans=0;
int ans=0,anss=0;
if(s[1].size()){
for(int i=1;i<=n;i++){
int mx=0;
while(siz()){
qwe s=begin();
dele(s);
// cout<<"w "<<s.p<<" "<<dels.top().p<<" "<<ads.top().p<<" "<<i<<" "<<" "<<dels.top().k<<" "<<ads.top().k;
if(s.p<=i){
// cout<<" h ";
if(s.p==i) mx=max(mx,s.k);
b[s.k]={0,0};
}
else{
insert(s);
break;
}
}
auto op=s[i].lower_bound(mx);
for(;op!=s[i].end();op++){
if(!vis[*op]) break;
}
if(op==s[i].end()){
getans=1;
break;
}
while(st[top]<*op && top>=1){
vis[st[top]]=0;
if(b[st[top]].k){
dele(b[st[top]]);
b[st[top]]={0,0};
}
anss=(anss-qpow(2,qz[st[top]])+mod)%mod;
top--;
}
st[++top]=*op;
vis[st[top]]=1;
anss=(anss+qpow(2,qz[st[top]]))%mod;
int wh=(~bt[*op])._Find_next(i);
if(wh<=n){
if(b[*op].p){
dele(b[*op]);
}
b[*op]={wh,*op};
insert(b[*op]);
}
ans=(ans+anss)%mod;
}
if(getans==1) printf("-1\n");
else printf("%lld\n",ans);
while(top){
b[st[top]]={0,0};
vis[st[top]]=0;
top--;
}
}
else{
printf("-1\n");
}
}
}
}

浙公网安备 33010602011771号