5月CWOI杂题
C0238 【0503 B组】模拟测试
垫底。
A 【1026 B组】bins
有一个结论:将两部分各自排序后对应装入是最优的,因为假如有交叉的选择,你交换后一定不劣。
发现 \(m\le1000\),可以把数列丢到值域上用后缀和判断。因为需要严格大于,可以挪一位。
code:
我写了线段树,其实不用。
点击查看代码
#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(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
int m,n,a[20005];
int cntA[20005],cntB[20005],A[20005],B[20005];
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 mi,tag;
}c[80005];
void pushup(int p){
c[p].mi=min(c[ls].mi,c[rs].mi);
}
void pushdown(int l,int r,int p){
if(!c[p].tag)return;
c[ls].mi+=c[p].tag,c[rs].mi+=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].mi=B[l]-A[l];return;}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(p);
}
void update(int l,int r,int p,int L,int R,int k){
if(L>R)return;
if(L<=l&&r<=R){
c[p].mi+=k,c[p].tag+=k;
return;
}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L>R)return 0;
if(L<=l&&r<=R){
return c[p].mi;
}
int mid=(l+r)>>1,res=inf;pushdown(l,r,p);
if(L<=mid)res=min(res,query(lson,L,R));
if(R>mid)res=min(res,query(rson,L,R));
return res;
}
}Tr;
signed main(){
m=read(),n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,j=n/2+1;i<=n/2;i++,j++)cntA[a[i]]++,cntB[a[j]]++;
for(int i=m;i>=1;i--)A[i]=A[i+1]+cntA[i];
for(int i=m-1;i>=1;i--)B[i]=B[i+1]+cntB[i+1];
Tr.build(1,m,1);
for(int k=n/2;k>=1;k--){
if(Tr.query(1,m,1,1,m)>=0)return printf("%lld\n",k),0;
Tr.update(1,m,1,1,a[k],1);
Tr.update(1,m,1,1,a[k]-1,1);
Tr.update(1,m,1,1,a[2*k-1]-1,-1);
Tr.update(1,m,1,1,a[2*k]-1,-1);
}
puts("0");
return 0;
}
B 【1026 B组】inversions
记 \(c_{i,0/1}\) 表示把该序列分成 \(2^{n-i}\) 个连续的长度均为 \(2^i\) 的段,每段左边对右边产生的逆/顺序对个数。手玩可以发现,对于一个 \(q_i=x\) 的操作,相当于对于所有 \(j\le i\),交换 \(c_{j,0}\) 和 \(c_{j,1}\)。初始的 c 数组可以用归并求。
code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
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;
}
ull k1,k2;
ull xorShift128Plus(){
ull k3=k1,k4=k2;
k1=k4;
k3^=(k3<<23);
k2=k3^k4^(k3>>17)^(k4>>26);
return k2+k4;
}
int a[1100005],b[1100005],q[1000005],p[1100005];
void gen(int n,int m,int threshold,ull _k1,ull _k2){
k1=_k1,k2=_k2;
for(int i=1;i<=(1<<n);i++)a[i]=xorShift128Plus()%threshold+1,p[i]=a[i];
for(int i=1;i<=m;i++)q[i]=xorShift128Plus()%(n+1);
}
int n,m,all,c[2][25],threshold;ull _k1,_k2;
void merge(int l,int r,int o,int t){
if(l==r)return;
int mid=(l+r)>>1;
merge(l,mid,o-1,t);merge(mid+1,r,o-1,t);
int i=l,j=mid+1,len=l;
while(i<=mid&&j<=r){
if(a[i]<=a[j])b[len++]=a[i++];
else b[len++]=a[j++],c[t][o]+=mid-i+1;
}
while(i<=mid)b[len++]=a[i++];
while(j<=r)b[len++]=a[j++];
for(int k=l;k<=r;k++)a[k]=b[k];
}
signed main(){
n=read(),m=read(),threshold=read(),_k1=read(),_k2=read();
gen(n,m,threshold,_k1,_k2);merge(1,1ll<<n,n,0);
for(int i=1;i<=(1ll<<n);i++)a[i]=-p[i];merge(1,1ll<<n,n,1);
for(int j=1,ans;j<=m;j++){
for(int i=0;i<=q[j];i++)swap(c[0][i],c[1][i]);
ans=0;for(int i=0;i<=n;i++)ans+=c[0][i];
all^=(ans*j);
}
printf("%lld\n",all);
return 0;
}
C 【1026 B组】candies
有一种较为暴力的做法:枚举我们要改的位置,对剩下的做一个背包,那么加入的物品一定会使得背包为 1 的位置变为两倍(不看 0)。要最大化答案,可以先找出最大的方案,再枚举加入物品的体积,过程可以用 bitset 优化。
(注意:下文中提到“为 0/1 的位置”均指的是在最优的方案中,不考虑会改变的元素,剩余的做背包的那个数组。)
提一嘴:虽然这个做法不太能过,但加上一个强力剪枝能过:发现对于为 1 的位置的集合 \({i_1,i_2\ldots i_s}\) 和为 0 的位置的集合 \({j_1,j_2\ldots j_t}\)(满足 \(j_1>i_1\),也就是 \(i_1\) 以前的为 0 的位置不管),\(i_k\) 在左移后必须安排到一个为 0 的位置,也就是至少为 \(j_k\)。我们枚举移动步数时可以利用这个结论跳一下,不用一个一个试。这样虽然挺玄学(我没写),但好像几乎卡不掉。
正解是你发现左移的步数 x 必须满足对于任意为 1 的位置 \(i<j\) 都要满足 \(j-i\neq x\)。有一个神奇的写法:
点击查看代码
B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
最后 B 中不为 1 的就是可行的答案。为什么这是对的?因为 \(j-i\) 这个式子可以看作有 \(n\) 个体积为 \(b_i\) 和 \(n\) 个体积为 \(-b_i\) 的物品做背包。
虽然能过,但还是有点慢,怎么优化?你可以先对所有物品跑背包,每次在这个基础上撤销掉这个物品的影响,得到的就是其余的背包。但是我们需要记录方案数,存不下怎么办?可以对大数取模,相信概率。
code:
点击查看代码
//每次暴力背包
#include<bits/stdc++.h>
#define int long long
using namespace std;
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;
}
bitset<1386005>B;int b[105],cnt[105];
signed main(){
int n=read(),id=1,s=0;
for(int i=1;i<=n;++i)b[i]=read(),s+=b[i];
sort(b+1,b+n+1);
for(int i=1;i<=n;++i){
if(b[i]==b[i-1])continue;
B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(i!=j)B|=(B<<b[j]);
cnt[i]=(int)B.count();
if(cnt[i]>cnt[id])id=i;
}
B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
for(int i=0;i<=1386000;++i)if(B[i]==0)return printf("%lld %lld\n",b[id],i),0;
return 0;
}
点击查看代码
//优化
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int b[105],s[105],cnt[105],f[700005],g[700005];bitset<700005>B;
signed main(){
int n,id=1,all=1;scanf("%d",&n);f[0]=1;for(int i=1;i<=n;++i)scanf("%d",&b[i]);
sort(b+1,b+n+1);for(int i=1;i<=n;++i)s[i]=s[i-1]+b[i];
for(int i=1;i<=n;++i)for(int j=s[i];j>=b[i];--j){
if(f[j]!=0)all--;
f[j]=(f[j]+f[j-b[i]])%mod;
if(f[j]!=0)all++;
}
for(int i=1;i<=n;++i){
if(b[i]==b[i-1])continue;
cnt[i]=all;
memcpy(g,f,sizeof(f));
for(int j=b[i];j<=s[n];++j){
if(g[j]!=0)cnt[i]--;
g[j]=(g[j]-g[j-b[i]]+mod)%mod;
if(g[j]!=0)cnt[i]++;
}
if(cnt[i]>cnt[id])id=i;
}
B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
for(int i=1;i<=s[n]-b[id]+1;++i)if(B[i]==0)return printf("%d %d\n",b[id],i),0;
return 0;
}
D 【1026 B组】sheep
还不会。
C0236 【0509 C组】模拟测试
欢 乐 赛。
A 【0505 C组】寻找羔羊
送分。
点击查看代码
#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;
}
char s[30005];
signed main(){
scanf("%s",s+1);int n=strlen(s+1),ans=0;
vector<int>v;
for(int i=1;i+4<=n;i++){
if(s[i]=='a'&&s[i+1]=='g'&&s[i+2]=='n'&&s[i+3]=='u'&&s[i+4]=='s')v.push_back(i);
}
for(int i=0;i<(int)v.size();i++){
int l=v[i],r=v[i]+4;
if(i==0)ans+=(l-1+1)*(n-r+1);
else ans+=(l-v[i-1])*(n-r+1);
}
printf("%lld\n",ans);
return 0;
}
B 【0505 C组】Find and Replace
原。
你可以根据题中的操作建一个 dag。更具体地,定义 \(p_{i,c}\) 表示“初始为字符 \(c\),经历 \([i,n]\) 的操作”。比如倒序枚举操作,每次 \(p_{i,c_i}\rightarrow p_{i+1,s_j}\)。最后求 \([l,r]\) 内的字符就是求第 \([l,r]\) 个叶子,dfs 即可。
两个细节:可能子树内叶子数会存不下,但是发现 \(l,r\le10^{18}\);一个字符变成另一个字符这种情况会构成链,跳起来很慢。但是你发现这两个点可以看成一个。
code:
点击查看代码
#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;
}
string c[200005],s[200005];
vector<int>g[6000005];
int T,root[200005][30],siz[6000005];
void dfs(int u,int l,int r){
if(g[u].empty()){
printf("%c",(char)(u-1+'a'));
return;
}
int L=l,R=r;
for(auto v:g[u]){
if(L>siz[v]){L-=siz[v],R-=siz[v];continue;}
dfs(v,max(1ll,L),min(siz[v],R));L-=siz[v],R-=siz[v];
if(R<=0)break;
}
}
signed main(){
int l=read(),r=read(),q=read();
for(int i=1;i<=q;i++)cin>>c[i]>>s[i];
c[0]="a",s[0]="a";
for(int o=0;o<26;o++)root[q+1][o]=++T,siz[root[q+1][o]]=1;
for(int i=q;i>=0;i--){
int o=c[i][0]-'a';
if((int)s[i].size()==1){
root[i][o]=root[i+1][s[i][0]-'a'];
}
else{
root[i][o]=++T;
for(int j=0;j<(int)s[i].size();j++){
g[root[i][o]].push_back(root[i+1][s[i][j]-'a']);
siz[root[i][o]]+=siz[root[i+1][s[i][j]-'a']];
if(siz[root[i][o]]>=inf)siz[root[i][o]]=inf;
}
}
for(int j=0;j<26;j++){
if(j!=o)root[i][j]=root[i+1][j];
}
}
dfs(root[0][0],l,r);
return 0;
}
C 【0505 C组】关灯
原。
唉,傻逼了。
因为异或运算的性质,可以把那个第一步的影响放到最后加进去。假设总共进行 \(m\) 次操作,第 \(i\) 次操作的第一步选了位置 \(x\),则这次操作会使得 \(x,x+1\ldots x+m-i\) 这一长度为 \(m-i+1\) 的子串翻转(当然,是循环的)。把它们倒过来看,就是第 \(i\) 次翻转长度为 \(i\) 的一段。定义 \(f_{i,j}\) 表示只考虑第一步的贡献,能否在第 \(i\) 步得到状态 \(j\)。这个因为 \(n\) 相等可以 \(O(n^22^n)\) 预处理。求答案就是枚举总的操作次数,则二、三步的影响是固定的,可以算出来。
可以证明答案上界至多为 \(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 T,n,f[25][1050005];
int shift(int pos,int len){
if(pos+len-1>=n)return (1ll<<n)-(1ll<<pos)+((1ll<<(pos+len-n))-1);
else return (1ll<<(pos+len))-(1ll<<pos);
}
int circle(int x){
int o=(x>>(n-1))&1ll;x-=(o<<(n-1));
return (x<<1)+o;
}
signed main(){
T=read(),n=read();f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1ll<<n);j++){
for(int k=0;k<n;k++){
f[i][j^shift(k,i)]|=f[i-1][j];
}
}
}
while(T--){
int a=0,b=0,s=0;
for(int i=0,o;i<n;i++)scanf("%1lld",&o),a|=(o<<i);
for(int i=0,o;i<n;i++)scanf("%1lld",&o),b|=(o<<i);
for(int i=0;i<=n;i++){
if(f[i][a^s]){printf("%lld\n",i);break;}
s^=b,b=circle(b);
}
}
return 0;
}
D 【0505 C组】哞路线
原。
思考 \(n=2\) 的特殊情况。结论:
解释:
第一种情况,0 到 2 \(\frac{a_1}{2}\) 次,1 到 2 \(\frac{a_2-a_1}{2}\) 次。因为 1 到 2 一定是在 0 到 2 的基础上选一个“寄生”得到,等价于 \(\frac{a_2-a_1}{2}\) 个东西放到 \(\frac{a_1}{2}\) 个盒子里,可以空,化简得到上式。
第二种情况,0 到 2 \(\frac{a_2}{2}\) 次,0 到 1 \(\frac{a_1-a_2}{2}\) 次。两种走法独立,相当于在 \(\frac{a_2}{2}+\frac{a_1-a_2}{2}\) 个东西中选 \(\frac{a_2}{2}\) 个作为第一类,化简得到上式。
扩展到一般的形式。对于 \((a_i,a_{i+1})\),这个子任务是独立的,把相邻的答案乘起来就是答案。
code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,SIZ=1e6;
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[1000005],iv[1000005],ij[1000005];
int C(int n,int m){
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int a[100005];
signed main(){
int n=read(),ans=1;for(int i=1;i<=n;i++)a[i]=read();
if(n==1)return puts("1"),0;
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=1;i<n;i++){
if(a[i]<a[i+1])ans=ans*C(a[i+1]/2-1,a[i]/2-1)%mod;
else ans=ans*C(a[i]/2,a[i+1]/2)%mod;
}
printf("%lld\n",ans);
return 0;
}
C0244 【0524 C组】模拟测试
蠢蠢。
A 【0902 BC组】数列
把生成树建出来,按顺序枚举每条边。红边可以直接染色,蓝边 \((u,v)\) 等价于 \(u\rightarrow v\) 路径上的红边需要在它之前染色。可以 dsu,也可以线段树。
点击查看代码
#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,w,nxt;
}e[1000005];
int head[500005],tot;
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int pa[500005][21],dep[500005],val[500005];
void dfslca(int u,int fa){
dep[u]=dep[fa]+1,pa[u][0]=fa;
for(int i=0;i<19;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,w=e[i].w;if(v==fa)continue;
val[v]=w;dfslca(v,u);
}
}
int getlca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=19;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=19;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
return pa[x][0];
}
int n,m,siz[500005],son[500005];
void dfs1(int u,int fa){
siz[u]=1,son[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs1(v,u);siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
int cur,dfn[500005],rnk[500005],top[500005];
void dfs2(int u,int rt){
top[u]=rt,dfn[u]=++cur,rnk[cur]=u;
if(son[u])dfs2(son[u],rt);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==pa[u][0])continue;
if(v!=son[u])dfs2(v,v);
}
}
vector<int>vec;
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 s,rn;
}c[2000005];
void pushup(int p){
c[p].s=(c[ls].s+c[rs].s);
c[p].rn=max(c[ls].rn,c[rs].rn);
}
void build(int l,int r,int p){
if(l==r){
c[p].s=0;
c[p].rn=l;
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].s=k;
if(k==0)c[p].rn=l;
else c[p].rn=-inf;
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return c[p].s;
int mid=(l+r)>>1,res=0;
if(L<=mid)res=res+query(lson,L,R);
if(R>mid)res=res+query(rson,L,R);
return res;
}
int ask(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return c[p].rn;
int mid=(l+r)>>1,res=-inf;
if(L<=mid)res=max(res,ask(lson,L,R));
if(R>mid)res=max(res,ask(rson,L,R));
return res;
}
void addPath(int u,int lca){
if(u==lca)return;
while(dep[top[u]]>dep[lca]){
// printf("{%lld,%lld}\n",u,top[u]);
while(1){
int pos=ask(1,n,1,dfn[top[u]],dfn[u]);
if(pos==-inf)break;
// printf("|%lld,%lld|---|%lld,%lld|\n",u,top[u],pos,rnk[pos]);
update(1,n,1,pos,1),vec.push_back(val[rnk[pos]]);
}
u=pa[top[u]][0];
}
if(u==lca)return;
while(1){
int pos=ask(1,n,1,dfn[lca],dfn[u]);
if(pos==-inf)break;
// printf("|%lld,%lld|---|%lld,%lld|\n",u,top[u],pos,rnk[pos]);
if(rnk[pos]!=lca)update(1,n,1,pos,1),vec.push_back(val[rnk[pos]]);
else break;
}
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
struct Path{
int u,v,c;
}g[500005];
int ans[500005];
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read(),m=read();
for(int i=1,u,v,c;i<=m;i++){
u=read(),v=read(),c=read();
g[i]=(Path){u,v,c};
if(c==1)add(u,v,i),add(v,u,i);
}
dfslca(1,0);dfs1(1,0);dfs2(1,1);
Tr.build(1,n,1);int col=0;
for(int i=1,u,v,c;i<=m;i++){
if(ans[i])continue;
u=g[i].u,v=g[i].v,c=g[i].c;
if(c==1){
ans[i]=++col;
if(pa[v][0]==u)Tr.update(1,n,1,dfn[v],1);
else Tr.update(1,n,1,dfn[u],1);
}
else{
int lca=getlca(u,v);vec.clear();
Tr.addPath(u,lca);
// printf("1 %lld YES\n",i);
Tr.addPath(v,lca);
// printf("2 %lld YES\n",i);
sort(vec.begin(),vec.end());
for(auto x:vec)ans[x]=++col;
ans[i]=++col;
}
}
for(int i=1;i<=m;i++)printf("%d ",ans[i]);
return 0;
}
B 【0902 BC组】方差
原题,我好蠢。
结论:我们一定会选一些点(其实就是分割区间的分割点),把会覆盖它们的区间全部删掉。
先假设所有区间都删掉。定义 \(f_i\) 表示 \(i\) 是最后一个分割点,能保留的最大和。转移的时候倒着枚举,维护前 k 大可以做到 \(n^2\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;
}
struct Line{
int l,r,w;
}s[2505];
int b[5005],f[5005];
vector<int>vec[5005];
signed main(){
freopen("interval.in","r",stdin);
freopen("interval.out","w",stdout);
int n=read(),k=read(),tot=0,all=0;
for(int i=1;i<=n;i++){
s[i].l=b[++tot]=read();
s[i].r=b[++tot]=read();
s[i].w=read();all+=s[i].w;
}
sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;i++){
s[i].l=lower_bound(b+1,b+tot+1,s[i].l)-b;
s[i].r=lower_bound(b+1,b+tot+1,s[i].r)-b;
vec[s[i].l].push_back(i);
}
f[0]=0;int ma=-inf;
for(int i=1;i<=tot;i++){
f[i]=0;
priority_queue<int,vector<int>,greater<int> >q;
int sum=0;
for(int j=i;j>=1;j--){
for(auto x:vec[j]){
if(s[x].r<=i){
sum+=s[x].w,q.push(s[x].w);
while((int)q.size()>k)sum-=q.top(),q.pop();
}
}
f[i]=max(f[i],f[j-1]+sum);
}
ma=max(ma,f[i]);
}
printf("%lld\n",all-ma);
return 0;
}
C 【1014 B组】简单题
定义 \(f_{l,r}\) 表示区间 \([l,r]\) 的答案,枚举区间最大值的位置 \(i\),有转移式 \(f_{l,r}=\max\limits_{i=l}^j\left\{f_{l,i-1}+f_{i+1,r}+\max\limits_{j=1}^{k_i}\left\{v_{i,j}\cdot g(l,r,i)-c_{i,j}\right\}\right\}\),其中 \(g(l,r,i)=\sum\limits_{x=l}^i\sum\limits_{y=i}^rq_{x,y}\)。
这里其实应该强制规定 \(v_{i,j}=\max\limits_{i=l}^r\left\{a_i\right\}\),但是我们发现假如区间最大值位置为 \(p\),则我们枚举从 \(p\) 转移的时候答案一定不劣,所以规定与否并不影响最终答案。
优化:求 \(\max\limits_{j=1}^{k_i}\left\{v_{i,j}\cdot g(l,r,i)-c_{i,j}\right\}\) 可以维护 \(n\) 个凸包,每次二分查找最大值。
点击查看代码
#include<bits/stdc++.h>
#define int __int128
#define y1 y3456
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;
}
inline void print(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)print(x/10);
putchar(x%10+'0');
}
int q[305][305],s[305][305],f[305][305];
int ask(int x1,int y1,int x2,int y2){
return s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
}
struct Point{
int x,y;
bool operator <(const Point &b)const{
if(x!=b.x)return x<b.x;
return y>b.y;
}
};
struct Convex{
vector<Point>v;
vector<int>q;
int k,L,R,id;
int P(int i,int j){
return (-v[j].y)-(-v[i].y);
}
int Q(int i,int j){
return (-v[j].x)-(-v[i].x);
}
void init(int n,int i){
k=n;id=i;L=1,R=0;q.resize(k+5);
v.push_back((Point){-inf,-inf});
}
void build(){
for(int i=1;i<=k;i++){
if(v[i].x==v[i-1].x)continue;
while(L<R&&P(q[R],i)*Q(q[R-1],q[R])>=P(q[R-1],q[R])*Q(q[R],i))R--;
q[++R]=i;
}
}
int find(int val){
int l=L+1,r=R,res=L;
while(l<=r){
int mid=(l+r)>>1;
if(P(q[mid-1],q[mid])<Q(q[mid-1],q[mid])*val)res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
int query(int l,int r){
int pos=find(ask(l,id,id,r));
return v[q[pos]].y-ask(l,id,id,r)*v[q[pos]].x;
}
}p[305];
signed main(){
int n=read();
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
q[i][j]=read();
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+q[i][j];
}
}
for(int i=1,k;i<=n;i++){
k=read();p[i].init(k,i);
for(int j=1,v,c;j<=k;j++){
v=read(),c=read();
p[i].v.push_back((Point){-v,-c});
}
sort(p[i].v.begin(),p[i].v.end());
p[i].build();
}
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;
}
}
for(int len=1;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int i=l;i<=r;i++){
f[l][r]=max(f[l][r],f[l][i-1]+f[i+1][r]+p[i].query(l,r));
}
}
}
print(f[1][n]);
return 0;
}
D 【1014 B组】数数题
先看 \(u=1\) 的部分分怎么做。定义 \(s_i\) 表示 \(a\) 的前缀和,\(d_i\) 表示 \(i\) 到根节点的期望距离,有 \(d_i=c_i+\sum\limits_{j=1}^{i-1}\dfrac{a_j}{s_{i-1}}(d_j+c_j)\),可以前缀和。
对于 \(u\neq1\) 的情况,答案就是 \(d_u+d_v-2\sum\limits_{l=1}^uP_ld_l\),其中 \(P_i\) 表示 \(i\) 是 \((u,v),u<v\) 的 lca 的概率。下面分 \(l\neq u\) 和 \(l=u\) 两种情况讨论:
-
\(l\neq u\):
对于每个点 \(o\),根据 \(o\) 的大小分类讨论:
-
\(u<o<v\):
此时点 \(o\) 只能在 \(l\rightarrow v\) 的路径上。
\[\begin{aligned} P&=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{u+1,u+2\ldots v-1\}}\dfrac{a_l}{s_{p_1-1}}\dfrac{a_{p_m}}{s_{v-1}}\prod\limits_{i=2}^m\dfrac{a_{p_{i-1}}}{s_{{p_i}-1}}\\ &=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{u+1,u+2\ldots v-1\}}\dfrac{a_l}{s_{v-1}}\prod\limits_{i=1}^m\dfrac{a_{p_i}}{s_{{p_i}-1}}\\ &=\dfrac{a_l}{s_{v-1}}\prod\limits_{i=u+1}^{v-1}(1+\dfrac{a_i}{s_{i-1}})\\ &=\dfrac{a_l}{s_{v-1}}\prod\limits_{i=u+1}^{v-1}\dfrac{s_i}{s_{i-1}}\\ &=\dfrac{a_l}{s_{v-1}}\dfrac{s_{v-1}}{s_x}\\ &=\dfrac{a_l}{s_x}\\ \end{aligned} \] -
\(l<o<u\):
此时点 \(o\) 即可以在 \(l\rightarrow u\) 的路径上,也可以在 \(l\rightarrow v\) 的路径上。
\[\begin{aligned} P&=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{l+1,l+2\ldots u-1\}}\dfrac{a_l}{s_{p_1-1}}\dfrac{2a_{p_m}}{s_{u-1}}\prod\limits_{i=2}^m\dfrac{2a_{p_{i-1}}}{s_{{p_i}-1}}\\ &=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{l+1,l+2\ldots u-1\}}\dfrac{a_l}{s_{u-1}}\prod\limits_{i=1}^m\dfrac{2a_{p_i}}{s_{{p_i}-1}}\\ &=\dfrac{a_l}{s_{u-1}}\prod\limits_{i=l+1}^{u-1}(1+\dfrac{2a_i}{s_{i-1}})\\ \end{aligned} \]这里为什么概率是二倍?根据式子,你把 \(l<o<u\) 放到两边的路径上都会在原来的概率上乘 \(\dfrac{a_o}{s_{o-1}}\),两种方案各算一遍就是乘 2 了。
两坨东西乘起来就是概率,都可以预处理。
-
-
\(l=u\):
延续刚刚 \(u<o<v\) 的式子,\(P=\dfrac{a_x}{s_x}\)。
直接做是 \(O(n\log P),P=10^9+7\) 的,但是优化一下求逆元可以 \(O(\log P+n)\)。
点击查看代码
#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;
}
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 a[1000005],s[1000005],c[1000005],d[1000005],e[1000005],f[1000005],k[1000005],p[1000005],v[1000005],jc[1000005],iv[1000005],ij[1000005];
int solve(int x,int y){
if(x==y)return 0;
if(x>y)swap(x,y);
int res=(d[x]+d[y])%mod;
res=(res-2*(s[x-1]*v[x]%mod+k[x]*d[x]%mod)%mod+mod)%mod;
return res;
}
signed main(){
int n=read(),q=read();
for(int i=1;i<n;i++)a[i]=read();
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++)c[i]=read();
jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*s[i]%mod;
ij[n]=qpow(jc[n],mod-2);for(int i=n-1;i>=1;i--)ij[i]=ij[i+1]*s[i+1]%mod;
for(int i=1;i<=n;i++)iv[i]=ij[i]*jc[i-1]%mod;
for(int i=1;i<=n;i++)v[i]=iv[i-1]*iv[i]%mod;
for(int i=1;i<=n;i++)k[i]=a[i]*iv[i]%mod;
d[1]=0,e[1]=(d[1]+a[1]*c[1]%mod)%mod;
for(int i=2;i<=n;i++)d[i]=(e[i-1]*iv[i-1]%mod+c[i])%mod,e[i]=(e[i-1]+(d[i]+c[i])%mod*a[i]%mod)%mod;
f[1]=1;for(int i=2;i<=n;i++)f[i]=(2*a[i]%mod*iv[i-1]+1)%mod;
jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*f[i]%mod;
ij[n]=qpow(jc[n],mod-2);for(int i=n-1;i>=1;i--)ij[i]=ij[i+1]*f[i+1]%mod;
for(int i=1;i<=n;i++)p[i]=a[i]*a[i]%mod*ij[i]%mod*d[i]%mod;
for(int i=1;i<=n;i++)v[i]=v[i]*jc[i-1]%mod;
for(int i=1;i<=n;i++)s[i]=(s[i-1]+p[i])%mod;
for(int i=1,u,v;i<=q;i++)u=read(),v=read(),printf("%lld\n",solve(u,v));
return 0;
}

浙公网安备 33010602011771号