7&8月CWOI杂题
C0281 【0704 B组】模拟测试
有一说一,这场挺简单。
A 【0630 BC组】棍子
二分。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
using namespace std;
const int inf=1e18;
const db eps=1e-9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,c,k,a[55];
int check(db mid){
if(mid==0)return 1;
int cnt=0,num=c;
for(int i=1;i<=n;i++){
if(a[i]<mid)continue;
if(num){
int t=min(num,(int)(a[i]/mid)-1);
num-=t,cnt+=t+1;
}
else cnt++;
}
return (cnt>=k);
}
signed main(){
n=read(),c=read(),k=read();
for(int i=1;i<=n;i++)a[i]=read();
db l=0,r=1000000000,res=0;
for(int i=1;i<=200;i++){
db mid=(l+r)/2.0;
if(check(mid))res=mid,l=mid;
else r=mid;
}
printf("%.10lf",res);
return 0;
}
B 【0630 BC组】数组
如果你枚举 \(a\),那么 \(b\) 的取值就是数轴上 \(\max(\lceil\frac{lc}{a}\rceil,lb)\) 到 \(\min(\lfloor\frac{rc}{a}\rfloor,rb)\) 这一段(可能会左端点大于右端点,需要对 0 取个 max)。\(\lceil\frac{lc}{a}\rceil\) 写成 \(\lfloor\frac{lc-1}{a}\rfloor+1\) 整除分块即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
int la=read(),ra=read(),lb=read(),rb=read(),lc=read(),rc=read(),ans=0;lc--;
for(int l=la,r;l<=ra;l=r+1){
r=min(ra,min(((lc/l==0)?ra:lc/(lc/l)),((rc/l==0)?ra:rc/(rc/l))));
ans+=(r-l+1)*max(min(rc/l,rb)-max(lc/l+1,lb)+1,0ll);
}
printf("%lld\n",ans);
return 0;
}
C 【0630 BC组】十一
根据小奥,我们知道一个数是 11 的倍数,当且仅当它从低往高数第奇数位(你可以认为下文中最低位为第一位)的数码之和,与偶数位的数码之和的差是 11 的倍数。那我们可以预处理出每个数奇数位数码之和与偶数位数码之和的差,这个记作 \(a_i\)。如果这个数最低位放在奇数位,那么它对最终构成的数的贡献是 \(a_i\),反之为 \(-a_i\)。
我们考虑先把奇数长度的放好再插入偶数长度的,因为这样新插入的不会影响之前的。定义 \(f_{i,j,k}\) 表示在前 \(i\) 个奇数长度的数中选 \(k\) 个使其贡献为正,总贡献在模 11 意义下为 \(j\) 的方案数。\(g_{i,j,k}\) 是处理偶数长度的,定义类似。
现在考虑插的过程。设奇数长度的有 \(c_0\) 个,偶数长度的有 \(c_1\) 个。枚举奇数长度的总贡献 \(i\),记 \(j=\lfloor\frac{c_0+1}{2}\rfloor\),有 \(k\) 个偶数长度的贡献为正。因为我们是先把奇数长度的放进去,所以贡献是一正一负交替出现的,故只看 dp 部分答案是 \(f_{c_0,i,j}\times g_{c_1,(11-i)\bmod 11,k}\)。容易发现长度奇偶性相同,贡献正负相同的数内部可以自由交换顺序,需要乘上全排列。
最后我们考虑把它们插进去,这个就是一个插板法搞搞的东西,插在正贡献的奇数长度的后面贡献为负,插在正贡献的奇数长度的后面贡献为负。最低位也可以插出正的贡献。
需要特判没有奇数长度的数的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,SIZ=1e5;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int jc[200005],iv[200005],ij[200005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int safemod(int x,int y){
return (x%y+y)%y;
}
int a[55],len[55],f[55][15][55],g[55][15][55];char s[55];
signed main(){
jc[0]=1;for(int i=1;i<=SIZ;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=SIZ;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=SIZ;i++)ij[i]=ij[i-1]*iv[i]%mod;
int n=read();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
int m=strlen(s+1),v=0;
for(int j=m;j>=1;j--){
if((m-j)%2==1)v=safemod(v-(s[j]-'0'),11);
else v=safemod(v+(s[j]-'0'),11);
}
a[i]=v,len[i]=m;
}
int c0=0,c1=0;f[0][0][0]=g[0][0][0]=1;
for(int o=1,i=0;o<=n;o++){
if(len[o]%2==0)continue;
i++,c0++;
for(int j=0;j<11;j++){
for(int k=0;k<=i;k++){
if(k)f[i][j][k]=safemod(f[i][j][k]+f[i-1][safemod(j-a[o],11)][k-1],mod);
if(i-k)f[i][j][k]=safemod(f[i][j][k]+f[i-1][safemod(j+a[o],11)][k],mod);
}
}
}
for(int o=1,i=0;o<=n;o++){
if(len[o]%2==1)continue;
i++,c1++;
for(int j=0;j<11;j++){
for(int k=0;k<=i;k++){
if(k)g[i][j][k]=safemod(g[i][j][k]+g[i-1][safemod(j-a[o],11)][k-1],mod);
if(i-k)g[i][j][k]=safemod(g[i][j][k]+g[i-1][safemod(j+a[o],11)][k],mod);
}
}
}
if(c0==0){
printf("%lld\n",jc[c1]*g[c1][0][c1]%mod);
return 0;
}
int ans=0;
for(int i=0;i<11;i++){
for(int j=(c0+1)/2;j<=(c0+1)/2;j++){
for(int k=0;k<=c1;k++){
int val=f[c0][i][j]*g[c1][safemod(-i,11)][k]%mod;
val=val*jc[j]%mod*jc[c0-j]%mod;
val=val*jc[k]%mod*jc[c1-k]%mod;
val=val*C(k+(c0-j),(c0-j))%mod;
val=val*C((c1-k)+j-1,j-1)%mod;
ans=safemod(ans+val,mod);
}
}
}
printf("%lld\n",ans);
return 0;
}
D 【0630 BC组】棋盘
学到了套路。
先把曼哈顿距离转成切比雪夫距离,坐标 \((x,y)\) 变换成 \((x+y,x-y)\)(题外话,切比雪夫距离也可以转化成曼哈顿距离,只用把 \((x,y)\) 变成 \((\frac{x+y}{2},\frac{x-y}{2})\) 即可)。
容易发现你每次一定是选隔得最远的两个点之一删掉,因为你这段距离现在删只用算一遍,一定是不劣的。所以搜索,map 记忆化一下即可。
点击查看代码
#include<bits/stdc++.h>
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
map<vector<pii>,int>f;
int dfs(vector<pii>v){
if(v.empty())return 0;
if(f.count(v))return f[v];
int a=0,b=0,c=0,d=0;
for(int i=0;i<(int)v.size();i++){
if(v[i].fi<v[a].fi)a=i;
if(v[i].fi>v[b].fi)b=i;
if(v[i].se<v[c].se)c=i;
if(v[i].se>v[d].se)d=i;
}
if(v[b].fi-v[a].fi>v[d].se-v[c].se){
vector<pii>v1=v,v2=v;v1.erase(v1.begin()+b),v2.erase(v2.begin()+a);
return f[v]=min(dfs(v1),dfs(v2))+(v[b].fi-v[a].fi);
}
else{
vector<pii>v1=v,v2=v;v1.erase(v1.begin()+d),v2.erase(v2.begin()+c);
return f[v]=min(dfs(v1),dfs(v2))+(v[d].se-v[c].se);
}
}
char s[70];vector<pii>v;
signed main(){
for(int i=1;i<=8;i++){
scanf("%s",s+1);
for(int j=1;j<=8;j++){
if(s[j]=='#')v.push_back(mk(i+j,i-j));
}
}
printf("%d\n",dfs(v));
return 0;
}
C297 【2023 暑期集训】 0707 NOIP模拟赛改错
不想多说。考的跟【数据删除】一样。
是原题场。
A 圣诞树
原题链接。
首先说一下什么是 prufer 序列。prufer 序列可以将一个带标号 n 个结点的树用 \([1,n]\) 中的 n-2 个整数表示。你也可以把它理解为完全图的生成树与数列之间的双射。prufer 序列是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复 n-2 次后就只剩下两个结点,算法结束。
好,我们暂时只需要这些。我们考虑对于每个点 \(i\),钦定它与其父亲相连的边的的
C315 【2023 暑期集训】0711 NOIP 模拟赛 改错
A 字符串
原题链接。简单,但挂分。怎么搞都可以。
B 交友
原题链接。
C 料理
原题链接。
D 象棋
还没改。
C333 【2023 暑期集训】0717 NOIP 模拟 改错
A 珠宝
原题链接。
直接背包你是会的,但是肯定会超时。根据直觉,我们大概率会选取“性价比”最高的一些物品,所以你可以排序,然后在最前面选一些、在剩下的随机选一些,对这些选出来的物品 dp 即可。
这是能过的,但是这是一个乱搞的做法,我们还是要正经一点的。
发现 \(c_i\) 很小,我们可以把 \(c_i\) 相同的当成一组来看。设 \(f_{i,j}\) 表示对于前 \(i\) 种物品,总体积为 \(j\) 的最大价值。令 \(s_{i,j}\) 表示所有体积为 \(i\) 的物品中价值前 \(j\) 大之和,\(cnt_i\) 表示体积为 \(i\) 的物品有多少个。有转移式 \(f_{i,j}=\max\limits_{k=0}^{cnt_i}\left\{f_{i-1,j-ik}+s_{i,k}\right\}\)。因为这个转移只在模 \(i\) 意义下相同的 \(j\) 有影响,我们把 \(j\) 写成 \(pi+q\) 的形式。容易证明此时价值满足四边形不等式,可以决策单调性优化。然后没了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,V=300;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int f[50005],g[50005],p[50005],c[1000005],v[1000005],t[305];vector<int>s[305],w[305];
void solve(int id,int l,int r,int L,int R){
if(l>r)return;
if(l==r){
g[l]=-1;
for(int i=L;i<=R&&i<=l;i++){
g[l]=max(g[l],f[p[i]]+s[id][l-i]);
}
return;
}
int mid=(l+r)>>1,pos=0;g[mid]=-1;
for(int i=L;i<=R&&i<=mid;i++){
if(mid-i>t[id])continue;
if(f[p[i]]+s[id][mid-i]>g[mid]){
g[mid]=f[p[i]]+s[id][mid-i];
pos=i;
}
}
solve(id,l,mid-1,L,pos);
solve(id,mid+1,r,pos,R);
}
int cmp(int x,int y){
return x>y;
}
signed main(){
int n=read(),m=read();
for(int i=1;i<=n;i++){
c[i]=read(),v[i]=read();
w[c[i]].push_back(v[i]);
}
for(int i=1;i<=V;i++){
if(w[i].empty())continue;
sort(w[i].begin(),w[i].end(),cmp);t[i]=(int)w[i].size();
s[i].resize(t[i]+5);s[i][1]=w[i][0];
for(int j=2;j<=t[i];j++)s[i][j]=s[i][j-1]+w[i][j-1];
}
for(int i=1;i<=V;i++){
if(w[i].empty())continue;
for(int j=0;j<i;j++){
int tot=0;
for(int k=j;k<=m;k+=i)p[++tot]=k;
solve(i,1,tot,1,tot);
for(int k=1;k<=tot;k++)f[p[k]]=g[k];
}
for(int j=1;j<=m;j++)f[i]=max(f[i-1],f[i]);
}
for(int i=1;i<=m;i++)printf("%lld ",f[i]);
return 0;
}
B 火灾
原题链接。没改错捏。
C 迷宫
原题链接。有点复杂的一道题。
D 小丑
原题链接。有点奇怪的一道题。
C346 【2023 暑期集训】0719 NOIP模拟 改错
部分分很多,好评。
A 镜子
比较典。可以把当前在哪个点,以及是从哪个方向转移过来的记为一个状态。显然多次在一个点放置镜子一定不优,01bfs 即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
const int dx[]={-1,1,0,0},dy[]={0,0,-1,1};
const int d1[]={3,2,1,0},d2[]={2,3,0,1};
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,sx,sy,ex,ey,d[1005][1005][5];
struct Node{
int x,y,o,k;
};
string s[1005];
int check(int x,int y){
return (x>=0&&x<=n-1&&y>=0&&y<=m-1&&s[x][y]!='X');
}
int bfs(){
for(int i=0;i<n;i++)for(int j=0;j<m;j++)for(int o=0;o<4;o++)d[i][j][o]=inf;
deque<Node>q;
for(int i=0;i<4;i++)q.push_back((Node){sx,sy,i,0}),d[sx][sy][i]=0;
while(!q.empty()){
Node u=q.front();q.pop_front();
int x=u.x,y=u.y,o=u.o,k=u.k,nx,ny,no,nk;
if(d[x][y][o]<k)continue;
if(s[x][y]!='/'&&s[x][y]!='\\'){
nx=x+dx[d1[o]],ny=y+dy[d1[o]],no=d1[o],nk=k+1;
if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_back((Node){nx,ny,no,nk});
nx=x+dx[d2[o]],ny=y+dy[d2[o]],no=d2[o],nk=k+1;
if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_back((Node){nx,ny,no,nk});
nx=x+dx[o],ny=y+dy[o],no=o,nk=k;
if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});
}
else if(s[x][y]=='/'){
nx=x+dx[d1[o]],ny=y+dy[d1[o]],no=d1[o],nk=k;
if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});
}
else{
nx=x+dx[d2[o]],ny=y+dy[d2[o]],no=d2[o],nk=k;
if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});
}
}
int res=inf;
for(int o=0;o<4;o++)res=min(res,d[ex][ey][o]);
return res;
}
signed main(){
n=read(),m=read();
for(int i=0;i<n;i++)getline(cin,s[i]);
for(int i=0;i<n;i++)for(int j=0;j<m;j++){
if(s[i][j]=='A')sx=i,sy=j;
if(s[i][j]=='B')ex=i,ey=j;
}
printf("%lld\n",bfs());
return 0;
}
B 匹配
原题链接。考虑按大小从小到大考虑:如果一头牛没有被安排进牛棚里,那么所有大小不小于它的牛棚就必须都不为空。
可以把牛和棚放到一起按大小排序(相同体积牛在前面),然后扫过去依次安排。定义 \(f_{i,j,0/1}\) 表示考虑这个混合序列的前 \(i\) 个,其中有 \(j\) 头牛需要分配的牛棚不在前 \(i\) 个里,是否有牛不会被分配牛棚。随便 DP 一下即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Node{
int op,x;
}a[6005];
int cmp(Node x,Node y){
if(x.x^y.x)return x.x<y.x;
return x.op<y.op;
}
int s[3005],t[3005],f[2][3005][2];
signed main(){
int n=read(),m=0;
for(int i=1;i<=n;i++)s[i]=read(),a[++m]=(Node){0,s[i]};
for(int i=1;i<=n;i++)t[i]=read(),a[++m]=(Node){1,t[i]};
sort(a+1,a+m+1,cmp);f[0][0][0]=1;
for(int i=1;i<=m;i++){
int o=i&1ll;
for(int j=0;j<=n;j++)f[o][j][0]=f[o][j][1]=0;
if(a[i].op==0){
for(int j=0;j<=n;j++){
if(j-1>=0)f[o][j][0]=(f[o][j][0]+f[o^1][j-1][0])%mod;
if(j-1>=0)f[o][j][1]=(f[o][j][1]+f[o^1][j-1][1])%mod;
f[o][j][1]=(f[o][j][1]+f[o^1][j][0])%mod;
f[o][j][1]=(f[o][j][1]+f[o^1][j][1])%mod;
}
}
else{
for(int j=0;j<=n;j++){
if(j+1<=n)f[o][j][0]=(f[o][j][0]+f[o^1][j+1][0]*(j+1)%mod)%mod;
if(j+1<=n)f[o][j][1]=(f[o][j][1]+f[o^1][j+1][1]*(j+1)%mod)%mod;
f[o][j][0]=(f[o][j][0]+f[o^1][j][0])%mod;
}
}
}
printf("%lld\n",(f[m&1][0][0]+f[m&1][0][1])%mod);
return 0;
}
C 修行
原题链接。问题相当于问 \(\sum\limits_{i=1}^{n-1}[p_i>p_{i+1}]=k\) 的排列 \(p\) 的数量。这就等价于问 \(\sum\limits_{i=1}^{n-1}[p_i<p_{i+1}]=n-1-k\) 的排列 \(p\) 的数量,欧拉数单点求一下即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,SIZ=1e5+2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
return res;
}
int jc[200005],iv[200005],ij[200005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int tot,p[100005],np[100005],f[100005];
signed main(){
int n=read(),m=read();m=n-m;f[1]=1;
jc[0]=1;for(int i=1;i<=SIZ;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=SIZ;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=SIZ;i++)ij[i]=ij[i-1]*iv[i]%mod;
for(int i=2;i<=SIZ;i++){
if(!np[i])p[++tot]=i,f[i]=qpow(i,n);
for(int j=1;j<=tot&&i*p[j]<=SIZ;j++){
np[i*p[j]]=1;f[i*p[j]]=f[i]*f[p[j]]%mod;
if(i%p[j]==0)break;
}
}
int res=0;
for(int i=0;i<=m;i++)res=(res+C(n+1,i)*f[m+1-i]%mod*((i&1)?-1:1)+mod)%mod;
printf("%lld\n",res);
return 0;
}
D 数数
原题链接。还不太会。
重庆市2023年中学友谊赛Round1
今天考了 cqbz 的联考,感觉非常恐怖啊,被爆杀。还好数据水\yum
不出所料的话接下来几天应该也是考联考,题目都在 cqbz 的 oj 上。
A. 猫捉老鼠
容易发现在基环树上,如果猫抓不到老鼠,当且仅当它们都在基环树的环上,且距离至少为 2。这个时候,老鼠就可以复制猫的动作,游戏就会永远进行下去。可以计算出老鼠走到环上的时间 \(t_1\) 和猫走到老鼠对应的点的时间 \(t_2\)。如果 \(t_1\ge t_2\),说明老鼠会被堵门;否则,就可以按上述方法一直躲下去。
记得特判环长不大于 3 的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[2000005];
int tot,head[1000005],deg[1000005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int cyc[1000005],dis[1000005],bel[1000005];
void dfs(int u,int d,int rt){
dis[u]=d,bel[u]=rt;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!cyc[v]&&!dis[v])dfs(v,d+1,rt);
}
}
int p[1000005];
void walk(int u,int id){
p[u]=id;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(cyc[v]&&!p[v])walk(v,id+1);
}
}
signed main(){
freopen("catch.in","r",stdin);
freopen("catch.out","w",stdout);
int n=read(),m=read(),siz=0;
for(int i=1,u,v;i<=n;i++)u=read(),v=read(),add(u,v),add(v,u);
queue<int>q;
for(int i=1;i<=n;i++)if(deg[i]==1)q.push(i);
while(!q.empty()){
int u=q.front();q.pop();deg[u]=-1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(deg[v]==-1)continue;
if((--deg[v])==1)q.push(v);
}
}
for(int i=1;i<=n;i++)if(deg[i]!=-1)cyc[i]=1,siz++;
for(int i=1;i<=n;i++)if(cyc[i])dfs(i,0,i);
for(int i=1;i<=n;i++){
if(!cyc[i])continue;
p[i]=1;
for(int j=head[i];j;j=e[j].nxt){
int v=e[j].v;
if(cyc[v]&&!p[v]){walk(v,2);break;}
}
break;
}
while(m--){
int x=read(),y=read();
if(siz<=3){puts("Yes");continue;}
int dx=dis[x],dy=dis[y]+min(abs(p[bel[x]]-p[bel[y]]),siz-abs(p[bel[x]]-p[bel[y]]));
if(dx>=dy){puts("Yes");continue;}
puts("No");continue;
}
return 0;
}
B. 格斗游戏
C. 大脑过载
D. 龙了个龙
很简单,但我没想到()
我们记数组 \(c_{i,j}\) 表示考虑 \(a_1\ldots a_i\),中间 \(j\) 出现了几次。容易发现 \([l,r]\) 合法当且仅当 \(\forall i\in[1,n],c_{l-1,i}\equiv c_{r,i}\pmod k\)。可以对 \(c\) 数组求 hash,于是问题转化为求区间有多少对值相同的数,莫队即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int base1=1303,mod1=100000357;
const int base2=10193,mod2=100000267;
const int T=1000639;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[100005],v1[100005],v2[100005],bel[100005],pw1[100005],pw2[100005],cnt[100005];ll res,ans[100005];
unordered_map<ll,int>mp;
struct Que{
int l,r,id;
}q[100005];
int cmp(Que x,Que y){
return bel[x.l]<bel[y.l]||(bel[x.l]==bel[y.l]&&(bel[x.l]&1?x.r<y.r:x.r>y.r));
}
signed main(){
freopen("dragon.in","r",stdin);
freopen("dragon.out","w",stdout);
int n=read(),k=read(),m=read();
pw1[0]=1;for(int i=1;i<=n;++i)pw1[i]=1ll*pw1[i-1]*base1%mod1;
pw2[0]=1;for(int i=1;i<=n;++i)pw2[i]=1ll*pw2[i-1]*base2%mod2;
for(int i=1;i<=n;++i){
a[i]=read();
v1[i]=(v1[i-1]-1ll*pw1[a[i]-1]*cnt[a[i]]%mod1+mod1)%mod1;
v2[i]=(v2[i-1]-1ll*pw2[a[i]-1]*cnt[a[i]]%mod2+mod2)%mod2;
cnt[a[i]]=(cnt[a[i]]+1)%k;
v1[i]=(v1[i]+1ll*pw1[a[i]-1]*cnt[a[i]]%mod1)%mod1;
v2[i]=(v2[i]+1ll*pw2[a[i]-1]*cnt[a[i]]%mod2)%mod2;
}
int siz=(int)sqrt(2*n);
for(int i=1;i<=n;++i)bel[i]=(i-1)/siz+1;
for(int i=1;i<=m;++i)q[i].l=read()-1,q[i].r=read(),q[i].id=i;
sort(q+1,q+m+1,cmp);
for(int i=1,L=1,R=0;i<=m;++i){
int l=q[i].l,r=q[i].r,id=q[i].id;
while(L>l)L--,res+=mp[1ll*T*v1[L]+v2[L]],mp[1ll*T*v1[L]+v2[L]]++;
while(R<r)R++,res+=mp[1ll*T*v1[R]+v2[R]],mp[1ll*T*v1[R]+v2[R]]++;
while(L<l)mp[1ll*T*v1[L]+v2[L]]--,res-=mp[1ll*T*v1[L]+v2[L]],L++;
while(R>r)mp[1ll*T*v1[R]+v2[R]]--,res-=mp[1ll*T*v1[R]+v2[R]],R--;
ans[id]=res;
}
for(int i=1;i<=m;++i)printf("%lld\n",ans[i]);
return 0;
}
C0299 【0828 B组】模拟测试
是 CW 的考试。
唉,不知道自己考试的时候在想些什么。
A 【0828 B组】交换
容易发现答案是所有逆序对的差的和。证明略。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,ans,p[1000005],d[1000005],c1[1000005],c2[1000005];
void add1(int x,int v){
for(;x<=n;x+=x&-x)c1[x]+=v;
}
void add2(int x,int v){
for(;x<=n;x+=x&-x)c2[x]+=v;
}
int ask1(int x){
int res=0;
for(;x;x-=x&-x)res+=c1[x];
return res;
}
int ask2(int x){
int res=0;
for(;x;x-=x&-x)res+=c2[x];
return res;
}
signed main(){
n=read();
for(int i=1;i<=n;i++)p[i]=read(),d[p[i]]=i,add1(i,p[i]),add2(i,1);
for(int i=1;i<=n;i++)ans+=ask1(d[i])-ask2(d[i])*i,add1(d[i],-i),add2(d[i],-1);
printf("%lld\n",ans);
return 0;
}
B 【0828 B组】游戏
唉,唉唉。
令 \(P_i\) 表示已经操作了 \(i\) 次,仍然没有消去 1 的概率。显然答案等于 \(\sum\limits_{i=0}^{k-1}P_i\)。
发现这个没有消去 1 等价于这些条件:\([a_1,a_2)\) 间操作次数不超过 0 次,\([a_1,a_3)\) 间操作次数不超过 1 次 \(\ldots\) 那么令 \(f_{i,j}\) 表示考虑前 \(i\) 个限制,操作了 \(j\) 次,没有消去 1 的概率。有转移式
且任何时候都有 \(j<i\)。时间复杂度 \(\mathcal{O}(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=1ll*b*b%mod)if(p&1)res=1ll*res*b%mod;
return res;
}
int jc[505],iv[505],ij[505];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return 1ll*jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int n,m,ivn,a[505],f[2][505];
signed main(){
scanf("%d%d",&n,&m),ivn=qpow(n%mod,mod-2);
jc[0]=1;for(int i=1;i<=m;i++)jc[i]=1ll*jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=m;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=m;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod;
f[0][0]=1,a[m]=n+1;for(int i=0;i<m;i++)scanf("%d",&a[i]);
for(int i=1;i<m;i++)for(int j=0;j<=i;j++){
int P=1ll*(a[i+1]-a[i])*ivn%mod,now=1;f[i&1][j]=0;
for(int k=0;k<=j;k++)f[i&1][j]=(f[i&1][j]+1ll*f[(i-1)&1][j-k]*now%mod*C(j,k)%mod)%mod,now=1ll*now*P%mod;
}
int ans=0;for(int i=0;i<m;i++)ans=(ans+f[(m-1)&1][i])%mod;
printf("%d\n",ans);
return 0;
}
C 【0828 B组】树题
唉,唉唉唉。
先看 \(type=0\)。定义 \(f_{u,0/1/2}\) 表示在 \(u\) 的子树内,节点 \(u\) 没被覆盖/\(u\) 被覆盖且儿子没被覆盖/\(u\) 和一个儿子被覆盖。有转移式
但是现在还要求所有方案里的四元组的数量,怎么办?可以把这两个信息压到一个二元组 \((a,b)\) 里。且
然后直接 dp。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
struct Info{
int x,y;
Info():x(0),y(0){}
Info(int _x,int _y):x(_x),y(_y){}
Info operator +(const Info &b)const{
return (Info){(x+b.x)%mod,(y+b.y)%mod};
}
Info operator *(const Info &b)const{
return (Info){1ll*x*b.x%mod,(1ll*y*b.x%mod+1ll*b.y*x%mod)%mod};
}
}f[500005][3];
void dfs(int u,int fa){
Info g[2][2],h[2][2][2];int now=0;
g[now][0]=h[now][0][0]=f[u][0]=f[u][1]=Info(1,0);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u),now^=1;
f[u][0]=f[u][0]*f[v][0],f[u][1]=f[u][1]*f[v][0];
g[now][0]=g[now^1][0]*f[v][0],g[now][1]=g[now^1][0]*f[v][1]+g[now^1][1]*f[v][0];
h[now][0][0]=h[now^1][0][0]*f[v][0],h[now][1][0]=h[now^1][0][0]*f[v][1]+h[now^1][1][0]*f[v][0];
h[now][0][1]=h[now^1][0][0]*f[v][2]+h[now^1][0][1]*f[v][0],h[now][1][1]=h[now^1][0][1]*f[v][1]+h[now^1][1][0]*f[v][2]+h[now^1][1][1]*f[v][0];
}
f[u][2]=g[now][1],f[u][0]=f[u][0]+h[now][1][1]*Info(1,1);
}
signed main(){
int n=read(),type=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs(1,0);
if(type==0)printf("%d\n",f[1][0].x);
else printf("%d\n%d\n",f[1][0].x,f[1][0].y);
return 0;
}
D 【0828 B组】木又寸题
唉,唉唉唉唉。
\(u=v\) 怎么做?我会离线点分治!
\(u\not=v\) 怎么做?我会拆成离 \(u\) 不超过 \(d\) 的 + 离 \(v\) 不超过 \(d\) 的 - 离 \(u,v\) 都不超过 \(d\) 的!
求离 \(u,v\) 都不超过 \(d\) 的怎么做?我会找 \(u\to v\) 的中点 \(m\),求离 \(m\) 不超过 \(d-\frac{\text{dist}(u,v)}{2}\) 的!
\(u\to v\) 中点是条边怎么办?我会对每条边拆点!
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[800005];
int tot,head[400005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int pa[400005][22],dep[400005];
void dfs(int u,int fa){
dep[u]=dep[fa]+1,pa[u][0]=fa;
for(int i=0;i<=20;i++)pa[u][i+1]=pa[pa[u][i]][i];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);
}
}
int getlca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=20;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
if(x==y)return x;
for(int i=20;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
return pa[x][0];
}
int jump(int u,int d){
for(int i=20;i>=0&&d;i--)if((d>>i)&1)u=pa[u][i],d-=(1<<i);
return u;
}
struct Que{
int val,d,id;
};
vector<Que>q[400005];
void addmid(int u,int v,int d,int id){
int lca=getlca(u,v),l=dep[u]+dep[v]-2*dep[lca];
q[u].push_back((Que){1,d,id}),q[v].push_back((Que){1,d,id});
if(d-l/2<0)return;
if(dep[u]-dep[lca]>=l/2)q[jump(u,l/2)].push_back((Que){-1,d-l/2,id});
else q[jump(v,l/2)].push_back((Que){-1,d-l/2,id});
}
int root,siz[400005],ma[400005],vis[400005];
void dfsroot(int u,int fa,int sum){
siz[u]=1,ma[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dfsroot(v,u,sum);siz[u]+=siz[v],ma[u]=max(ma[u],siz[v]);
}
ma[u]=max(ma[u],sum-siz[u]);
if(ma[u]<ma[root])root=u;
}
int num,a[400005],dis[400005],bel[400005];vector<int>p[400005];
void dfsdis(int u,int fa){
a[++num]=u,p[bel[u]].push_back(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dis[v]=dis[u]+1,bel[v]=bel[u],dfsdis(v,u);
}
}
int n,m,ans[200005];
struct BIT{
int c[400005];
void add(int x,int y){
x++;for(;x<=2*n;x+=x&-x)c[x]+=y;
}
int ask(int x){
if(x<0)return 0;
int res=0;x++;
for(;x;x-=x&-x)res+=c[x];
return res;
}
}Tr;
void calc(int u){
num=0,a[++num]=u,dis[u]=0,bel[u]=u;
vector<int>tmp;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(vis[v])continue;
dis[v]=1,bel[v]=v,dfsdis(v,u);tmp.push_back(bel[v]);
}
for(int i=1;i<=num;i++){
if(a[i]<=n)Tr.add(dis[a[i]],1);
}
for(int i=1;i<=num;i++){
for(auto x:q[a[i]]){
int val=x.val,d=x.d,id=x.id;
ans[id]+=val*Tr.ask(d-dis[a[i]]);
}
}
for(int i=1;i<=num;i++){
if(a[i]<=n)Tr.add(dis[a[i]],-1);
}
for(auto r:tmp){
for(auto i:p[r])if(i<=n)Tr.add(dis[i],1);
for(auto i:p[r]){
for(auto x:q[i]){
int val=x.val,d=x.d,id=x.id;
ans[id]-=val*Tr.ask(d-dis[i]);
}
}
for(auto i:p[r])if(i<=n)Tr.add(dis[i],-1);
p[r].clear();
}
}
void solve(int u){
vis[u]=1,calc(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(vis[v])continue;
root=0,dfsroot(v,0,siz[v]),solve(root);
}
}
signed main(){
n=read(),m=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,i+n),add(i+n,u),add(v,i+n),add(i+n,v);
dfs(1,0);
for(int i=1,u,v,d;i<=m;i++)u=read(),v=read(),d=min(n-1,read())*2,addmid(u,v,d,i);
ma[0]=inf;root=0;dfsroot(1,0,2*n-1);solve(root);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
重庆市2023年中学友谊赛Round4
第四场联考!这一场 CWOI 上也有题目。
A. 月华
稍微想复杂了点。
因为题目不保证 \(P\) 是质数,直接二维前缀积显然是不行的。考虑分解出 \(P\) 中的质因子,集合记作 \(S\)。对于 \(a_{i,j}\),它的因子有两种情况:如果不在 \(S\) 中,就可以直接前缀积送走;反之,可以枚举 \(S\) 中每一个元素 \(p_i\),预处理前缀和后对每个询问算出区间内有多少个 \(p_i\),直接算对应次幂即可。
时间复杂度大概是 \(\mathcal{O}(\log P+nm+q(\omega(P)+\log P))\)?反正是个非常宽松的上界,可以线性求逆元优化成这样。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define y1 y3456
using namespace std;
const int mod=147744151;
char buf[(1<<15)+5],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<15,stdin),p1==p2)?EOF:*p1++)
int read(){
int f=0,x=0;char ch=getchar();
while(!isdigit(ch)){f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f?-x:x;
}
#define ull unsigned long long
#define ui128 __uint128_t
struct Barrett{
ull d;ui128 m;
void init(ull _d){
d=_d,m=(((ui128)(1)<<64)/d);
}
ull operator()(ull a){
ull w=(m*a)>>64;w=a-w*d;
if(w>=d)w-=d;return w;
}
}MOD;
int tot,B[15];
inline void init(int P){
for(int i=2;1ll*i*i<=P;++i){
if(P%i==0){
B[++tot]=i;
while(P%i==0)P/=i;
}
}
if(P>1)B[++tot]=P;
}
int n,m,P,q,a[1005][1005],iva[1005][1005],A[1000005],s[1005][1005],p[1005][1005],ip[1005][1005];
inline int exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return a;}
int g=exgcd(b,a%b,x,y),z=x;x=y;y=z-a/b*y;return g;
}
inline int inv(int a){
int x,y;exgcd(a,P,x,y);
return MOD(x%P+P);
}
int Qx1[2000005],Qy1[2000005],Qx2[2000005],Qy2[2000005],res[2000005],t[10005],jc[1000005],ij[1000005],iv[1000005];
signed main(){
n=read(),m=read(),P=read();init(P);t[0]=1;MOD.init(P);
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)a[i][j]=read();
q=read();
for(int i=1;i<=q;++i)Qx1[i]=read(),Qy1[i]=read(),Qx2[i]=read(),Qy2[i]=read(),res[i]=1;
for(int k=1;k<=tot;++k){
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
int num=0;while(a[i][j]%B[k]==0)a[i][j]/=B[k],num++;
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+num;
}
for(int i=1;i<=s[n][m];++i)t[i]=MOD((ll)t[i-1]*B[k]);
for(int i=1;i<=q;++i){
int x1=Qx1[i],y1=Qy1[i],x2=Qx2[i],y2=Qy2[i];
res[i]=MOD((ll)res[i]*t[s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]]);
}
}
jc[0]=1;for(int i=1;i<=n*m;++i)jc[i]=MOD((ll)jc[i-1]*a[(i-1)/m+1][(i-1)%m+1]);
ij[n*m]=inv(jc[n*m]);for(int i=n*m-1;i>=1;--i)ij[i]=MOD((ll)ij[i+1]*a[i/m+1][i%m+1]);
for(int i=1;i<=n*m;++i)iva[(i-1)/m+1][(i-1)%m+1]=MOD((ll)jc[i-1]*ij[i]);
for(int i=0;i<=n;++i)p[i][0]=ip[i][0]=1;
for(int j=0;j<=m;++j)p[0][j]=ip[0][j]=1;
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
p[i][j]=MOD(MOD(MOD((ll)p[i-1][j]*p[i][j-1])*ip[i-1][j-1])*a[i][j]);
ip[i][j]=MOD(MOD(MOD((ll)ip[i-1][j]*ip[i][j-1])*p[i-1][j-1])*iva[i][j]);
}
for(int i=1;i<=q;++i){
int x1=Qx1[i],y1=Qy1[i],x2=Qx2[i],y2=Qy2[i];
res[i]=MOD((ll)res[i]*(MOD(MOD(MOD((ll)p[x2][y2]*ip[x1-1][y2])*ip[x2][y1-1])*p[x1-1][y1-1])));
}
int ans=0;
for(int i=1;i<=q;++i)ans=(ans+(i^res[i]))%mod;
printf("%d",ans);
return 0;
}
C. 夏虫
没在线段树上修改 \(a_i\) 的值调半天,难绷。
如果没有修改操作,我们可以定义 \(f_i\) 表示开始位置为 \(i\) 的答案,然后找两边第一个比它大的转移,记忆化搜索,这是 \(\mathcal{O}(n\log n)\) 的。
加上修改操作后,原先的方法并不好维护。可以发现令 \(L_i,R_i\) 表示 \(a_1\ldots a_i\) 的后缀最大值集合与 \(a_i\ldots a_n\) 的前缀最大值集合,\(f_i=|L_i\cup R_i|-1\),思考这个交换操作会让哪些 \(L,R\) 发生变化。
假设 \(a_x<a_{x+1}\)。令这两个数左右两边第一个不小于 \(a_x\) 的位置为 \(i,j\),那么 \(L_{i+1}\ldots L_{x-1}\) 和 \(R_{x+2}\ldots R_{j-1}\) 可能会发生变化(我们单独考虑 \(x,x+1\) 的变化)。更具体的,以 \(L\) 为例,如果这些 \(L\) 里有 \(a_x\),那么答案并不会变,否则会减一。怎么判断?\(a_i\) 是第一个不小于 \(a_x\) 的位置,如果 \(a_p=a_x\) 存在那么一定有 \(a_i=a_x\)。其他情况类似。
对于 \(x,x+1\),我们可以在求出其它点的答案后沿用最开始的方法。时间复杂度 \(\mathcal{O}((n+q)\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,q,a[100005];
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int f,tag,ma;
}c[400005];
void pushup(int p){
c[p].f=c[ls].f+c[rs].f;
c[p].ma=max(c[ls].ma,c[rs].ma);
}
void pushdown(int l,int r,int p){
if(!c[p].tag)return;
int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
c[ls].f+=ln*c[p].tag,c[rs].f+=rn*c[p].tag;
c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
c[p].tag=0;
}
void build(int l,int r,int p){
c[p].tag=0;
if(l==r){c[p].ma=a[l];c[p].f=-1;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int x,int k){
if(l==r){c[p].f=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(p);
}
void change(int l,int r,int p,int x,int k){
if(l==r){c[p].ma=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(x<=mid)change(lson,x,k);
else change(rson,x,k);
pushup(p);
}
void add(int l,int r,int p,int L,int R,int k){
if(L>R)return;
if(L<=l&&r<=R){c[p].f+=(r-l+1)*k,c[p].tag+=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)add(lson,L,R,k);
if(R>mid)add(rson,L,R,k);
pushup(p);
}
int ask(int l,int r,int p,int L,int R){
if(L>R)return 0;
if(L<=l&&r<=R)return c[p].f;
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res+=ask(lson,L,R);
if(R>mid)res+=ask(rson,L,R);
return res;
}
int query(int l,int r,int p,int L,int R){
if(L>R)return -inf;
if(L<=l&&r<=R)return c[p].ma;
int mid=(l+r)>>1,res=-inf;pushdown(l,r,p);
if(L<=mid)res=max(res,query(lson,L,R));
if(R>mid)res=max(res,query(rson,L,R));
return res;
}
int Findl(int l,int r,int p,int x,int k){
if(x<1)return 0;
if(l==r){return (c[p].ma>=k)?l:0;}
int mid=(l+r)>>1,res=0;
if(x>mid){
if(c[rs].ma>=k)res=Findl(rson,x,k);
if(res==0&&c[ls].ma>=k)res=Findl(lson,x,k);
}
else if(c[ls].ma>=k)res=Findl(lson,x,k);
return res;
}
int Findr(int l,int r,int p,int x,int k){
if(x>n)return n+1;
if(l==r){return (c[p].ma>=k)?l:n+1;}
int mid=(l+r)>>1,res=n+1;
if(x<=mid){
if(c[ls].ma>=k)res=Findr(lson,x,k);
if(res==n+1&&c[rs].ma>=k)res=Findr(rson,x,k);
}
else if(c[rs].ma>=k)res=Findr(rson,x,k);
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int dfs(int x){
if(Tr.ask(1,n,1,x,x)>=0)return Tr.ask(1,n,1,x,x);
int L=Tr.Findl(1,n,1,x-1,a[x]+1),R=Tr.Findr(1,n,1,x+1,a[x]+1);
if(L==0&&R==n+1)return Tr.update(1,n,1,x,0),0;
else if(L==0)return Tr.update(1,n,1,x,dfs(R)+1),dfs(R)+1;
else if(R==n+1)return Tr.update(1,n,1,x,dfs(L)+1),dfs(L)+1;
else if(a[L]!=a[R])return Tr.update(1,n,1,x,((a[L]>a[R])?dfs(R):dfs(L))+1),((a[L]>a[R])?dfs(R):dfs(L))+1;
else return Tr.update(1,n,1,x,max(dfs(L),dfs(R))+1),max(dfs(L),dfs(R))+1;
}
signed main(){
n=read(),m=read();a[0]=a[n+1]=inf;
for(int i=1;i<=n;i++)a[i]=read();
Tr.build(1,n,1);
for(int i=1;i<=n;i++)dfs(i);
q=read();
while(q--){
int x=read(),l=read(),r=read(),p;
Tr.change(1,n,1,x,a[x+1]);Tr.change(1,n,1,x+1,a[x]);
if(a[x]<a[x+1]){
if(x>1){
p=Tr.Findl(1,n,1,x-1,a[x]);
if(a[p]!=a[x])Tr.add(1,n,1,p+1,x-1,-1);
}
if(x+1<n){
p=Tr.Findr(1,n,1,x+2,a[x]);
if(a[p]!=a[x])Tr.add(1,n,1,x+2,p-1,1);
}
}
else if(a[x]>a[x+1]){
if(x>1){
p=Tr.Findl(1,n,1,x-1,a[x+1]);
if(a[p]!=a[x+1])Tr.add(1,n,1,p+1,x-1,1);
}
if(x+1<n){
p=Tr.Findr(1,n,1,x+2,a[x+1]);
if(a[p]!=a[x+1])Tr.add(1,n,1,x+2,p-1,-1);
}
}
swap(a[x],a[x+1]);Tr.update(1,n,1,x,-1);Tr.update(1,n,1,x+1,-1);
Tr.update(1,n,1,x,dfs(x));Tr.update(1,n,1,x+1,dfs(x+1));
printf("%lld\n",Tr.ask(1,n,1,l,r)*m+(r-l+1)*(n-1));
}
return 0;
}
D. 万分之一的光
比较精巧的一道题?
如果按照题意去考虑删点的顺序,会发现完全无法记录状态,考虑转化。可以把原问题看做给树上每条边定向,按拓扑序删点。这样可以 \(\mathcal{O}(2^nn)\)。更进一步的,此时的价值也可以继续拆分。如果 \(i\) 可以走到 \(s_i\) 个点,答案就是 \(\sum\limits_{i=1}^na_is_i\)。
定义 \(f_{i,j}\) 表示 \(i\) 在自己的子树内可以到达 \(j\) 个点,且父边定向为 \(fa_i\to i\) 的最大价值;\(g_{i,j}\) 表示 \(i\) 在自己的子树外可以到达 \(j\) 个点,且父边定向为 \(i\to fa_i\) 的最大价值。对于从儿子 \(v\) 转移到当前节点 \(u\),再定义 \(h_{i,q,j,k}\) 表示强制钦定 \(i\) 最终能到达 \(q\) 个点,目前只考虑了前 \(j\) 个儿子,且当前能到达子树内 \(k\) 个点的最大价值。有
\(h\) 可以只保留两维,如果最外层枚举 \(q\) 可以只保留一维。同时,从 \(h\) 转移回 \(f,g\) 也有
时间复杂度 \(\mathcal{O}(n^3)\),空间复杂度 \(\mathcal{O}(n^2)\)。时间复杂度是对的是因为里面一层等价于树形背包。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[805];
int tot,head[405];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,a[405],siz[405],f[405][405],g[405][405],h[405],t[405];
void dfs(int u,int fa){
for(int i=1;i<=n;i++)f[u][i]=g[u][i]=-inf;
siz[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);siz[u]+=siz[v];
}
for(int q=1;q<=n;q++){
for(int j=1;j<=n;j++)h[j]=t[j]=-inf;
h[1]=q*a[u];int now=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
for(int j=1;j<=now;j++)t[j]=max(t[j],h[j]+g[v][q]);
for(int j=1;j<=now;j++)for(int k=1;k<=siz[v]&&j+k<=n;k++)t[j+k]=max(t[j+k],h[j]+f[v][k]);
now=min(now+siz[v],q);
for(int j=1;j<=now;j++)h[j]=t[j],t[j]=-inf;
}
f[u][q]=h[q];
for(int j=1;j<=q;j++)g[u][q-j]=max(g[u][q-j],h[j]);
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs(1,0);int ans=-inf;
for(int i=1;i<=n;i++)ans=max(ans,f[1][i]);
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号