省选测试4
总结
考了三道数据结构的题
凭着暴力分到了 \(rank4\)
三道题全场没有一个人 \(AC\)
可能这种情况下就是比谁更发挥得稳定
A. 点点的圈圈
分析
因为圆与圆之间不会相交,所以可以把圆与圆之间的关系看成父子关系
建好边之后就可以做一遍树形 \(dp\)
设 \(f[x]\) 为以 \(x\) 为根的子树中选出互不包含的两个点的最大价值
则 \(f[x]=max(val[x],\sum_{fa[u]=x}f[u])\)
做一遍树形 \(dp\) 的复杂度是 \(O(n)\) 的,关键在于建图
暴力的建图方式是按照半径从大到小排序
每扫到一个圆就在前面找第一个包含它的圆建边
\(n^2\) 显然过不去
考虑用扫描线的思想优化
对于一个圆,我们在扫到它的最左端的时候把它加入,扫到最右端的时候把它删除,同时把它拆成上下两个半圆
用线段树维护是肯定不行的,因为每一个圆与扫描线的交点会随着横坐标的变化而变化
可以用换成平衡树
第一关键字为与扫描线的交点的纵坐标从大到小,第二关键字为上半圆大于下半圆
在加入一个圆之前,我们在平衡树里找一下这个圆的后继
如果找到的后继是上半圆,说明这两个圆是兄弟关系,父亲设为一个
否则就是父子关系
时间复杂度 \(O(nlogn)\)
常数比较大,在某个毒瘤加强数据后吸氧才能过
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<set>
#include<cstring>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=1e5+5;
int n,h[maxn],val[maxn],tot=1,fa[maxn],f[maxn],ans,sta[maxn<<1],tp,a1[maxn],a2[maxn],x;
struct asd{
int to,nxt;
}b[maxn<<1];
inline void ad(rg int aa,rg int bb){
b[tot].to=bb;
b[tot].nxt=h[aa];
h[aa]=tot++;
}
struct Node{
int nx,ny,nr,nw,id;
}tmp[maxn];
struct jie{
int haha,isup;
inline double js(){
return 1.0*tmp[haha].ny+1.0*sqrt(1.0*tmp[haha].nr*tmp[haha].nr-1.0*(tmp[haha].nx-sta[x])*(tmp[haha].nx-sta[x]))*isup;
}
inline friend bool operator < (rg jie A,rg jie B){
if(A.js()==B.js()) return A.isup>B.isup;
return A.js()>B.js();
}
};
std::set<jie> s;
#define sit std::set<jie>::iterator
bool vis[maxn];
void dfs(rg int now){
vis[now]=1;
rg int sum=0;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==fa[now]) continue;
dfs(u);
sum+=f[u];
}
f[now]=std::max(val[now],sum);
}
std::vector<int> g1[maxn<<1],g2[maxn<<1];
int main(){
memset(h,-1,sizeof(h));
n=read();
rg int nx,nr,nw,wz;
rg sit it;
for(rg int i=1;i<=n;i++) tmp[i].nx=read(),tmp[i].ny=read(),tmp[i].nr=read(),tmp[i].nw=read();
for(rg int i=1;i<=n;i++){
nx=tmp[i].nx,nr=tmp[i].nr,nw=tmp[i].nw;
tmp[i].id=i;
sta[++tp]=nx-nr,sta[++tp]=nx+nr;
a1[i]=nx-nr,a2[i]=nx+nr;
val[i]=nw;
}
std::sort(sta+1,sta+1+tp);
tp=std::unique(sta+1,sta+1+tp)-sta-1;
for(rg int i=1;i<=n;i++){
wz=std::lower_bound(sta+1,sta+tp+1,a1[i])-sta;
g1[wz].push_back(i);
wz=std::lower_bound(sta+1,sta+tp+1,a2[i])-sta;
g2[wz].push_back(i);
}
for(x=1;x<=tp;x++){
for(rg int j=0;j<g2[x].size();j++){
wz=g2[x][j];
s.erase((jie){wz,-1}),s.erase((jie){wz,1});
}
for(rg int j=0;j<g1[x].size();j++){
wz=g1[x][j];
if(!s.empty()){
it=s.upper_bound((jie){wz,-1});
if(it!=s.end()){
if(it->isup==1) fa[wz]=fa[it->haha];
else fa[wz]=it->haha;
}
}
s.insert((jie){wz,-1}),s.insert((jie){wz,1});
}
}
for(rg int i=1;i<=n;i++){
if(fa[i]){
ad(i,fa[i]),ad(fa[i],i);
}
}
for(rg int i=1;i<=n;i++){
if(fa[i]==0){
dfs(i);
ans+=f[i];
}
}
printf("%d\n",ans);
return 0;
}
B. 点点的计算
分析
是第 \(n\) 行前 \(k\) 的个数的最小公倍数为 \(s[n][k]\)
易证(打表可得)\(s[n][k]=lcm(n-k+1,...,n-1,n)\)
暴力的做法是对于每一个数筛出它的所有质因子以及次数
统计的时候遇到没出现过的质因子或者出现过但次数比之前大就暴力乘起来
可以得到 \(40\) 分的好成绩
考虑用主席树去优化
主席树的每一个根节点 \(rt[i]\) 存储的是 \(s[i][1] \sim s[i][i]\) 的答案
对于一开始的 \(rt[1]\) ,答案为 \(1\)
关键在于如何由 \(rt[i-1]\) 转移到 \(rt[i]\)
首先,\(s[i][i]=i\) ,所以肯定要把下标为 \(i\) 的位置乘上一个 \(i\)
然后对于 \(i\) 进行质因数分解
对于每一个质因数,我们开一个 \(vector\) 存储所有它出现过的位置,以及它的次数
每次把 \(vector\) 最后的元素拿出来和 \(i\) 分解出来的进行比较
如果 \(vector\) 中元素的次数比较小,那么在主席树对应的位置上把该元素的答案除掉,同时把元素扔出去,分解出来的数也要把这个贡献除掉
否则在线段树对应的位置把分解出来的那部分的答案除掉,把 \(vector\) 中的元素也除掉这个贡献,然后退出循环
主席树上再维护一个区间乘积就可以了
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=2e5+5,mod=1e9+7;
int q,A,B,Mod,C[maxn],D[maxn],vis[maxn],n,k,mmax,pri[maxn],f[maxn];
bool not_pri[maxn];
int Min(rg int aa,rg int bb){
return aa<bb?aa:bb;
}
void xxs(){
not_pri[0]=not_pri[1]=1;
f[1]=1;
for(rg int i=2;i<=Mod;i++){
if(!not_pri[i]){
pri[++pri[0]]=i;
f[i]=i;
}
for(rg int j=1;j<=pri[0] && i*pri[j]<=Mod;j++){
not_pri[i*pri[j]]=1;
if(i%pri[j]==0){
f[i*pri[j]]=Min(f[i],pri[j]);
break;
}
f[i*pri[j]]=pri[j];
}
}
}
int ksm(rg int ds,rg int zs){
rg int nans=1;
while(zs){
if(zs&1) nans=1LL*nans*ds%mod;
ds=1LL*ds*ds%mod;
zs>>=1;
}
return nans;
}
struct trr{
int lch,rch,sum;
}tr[maxn*80];
int cnt,rt[maxn],rk[maxn],ncnt,ny[maxn],ans;
struct jie{
int id,val;
jie(){}
jie(rg int aa,rg int bb){
id=aa,val=bb;
}
};
std::vector<jie> g[maxn];
int build(rg int da,rg int l,rg int r){
if(!da) da=++cnt;
tr[da].sum=1;
if(l==r) return da;
rg int mids=(l+r)>>1;
tr[da].lch=build(tr[da].lch,l,mids);
tr[da].rch=build(tr[da].rch,mids+1,r);
return da;
}
int ad(rg int da,int pre,rg int l,rg int r,rg int wz,rg int val){
da=++cnt;
tr[da]=tr[pre];
tr[da].sum=1LL*tr[da].sum*val%mod;
if(l==r) return da;
rg int mids=(l+r)>>1;
if(wz<=mids) tr[da].lch=ad(tr[da].lch,tr[pre].lch,l,mids,wz,val);
else tr[da].rch=ad(tr[da].rch,tr[pre].rch,mids+1,r,wz,val);
return da;
}
int cx(rg int da,rg int l,rg int r,rg int nl,rg int nr){
if(l>=nl && r<=nr) return tr[da].sum;
rg int mids=(l+r)>>1,nans=1;
if(nl<=mids) nans=1LL*nans*cx(tr[da].lch,l,mids,nl,nr)%mod;
if(nr>mids) nans=1LL*nans*cx(tr[da].rch,mids+1,r,nl,nr)%mod;
return nans;
}
void updat(rg int id,rg int pri,rg int haha){
if(!rk[pri]) rk[pri]=++ncnt;
rg int now=rk[pri],cs=0,tmp=haha;
while(g[now].size()){
cs=g[now].size()-1;
if(g[now][cs].val>haha){
g[now][cs].val/=haha;
rt[id]=ad(rt[id],rt[id],1,Mod,g[now][cs].id,ny[haha]);
break;
} else {
haha/=g[now][cs].val;
rt[id]=ad(rt[id],rt[id],1,Mod,g[now][cs].id,ny[g[now][cs].val]);
g[now].pop_back();
}
}
g[now].push_back(jie(id,tmp));
}
void pre(rg int id){
rg int now=id,haha=1;
rt[id]=ad(rt[id],rt[id-1],1,Mod,id,id);
rg int cs;
while(now!=1){
cs=f[now],haha=1;
while(now%cs==0){
now/=cs,haha*=cs;
}
updat(id,cs,haha);
}
}
int main(){
q=read(),n=read(),k=read(),A=read(),B=read(),Mod=read();
xxs();
rt[1]=build(rt[1],1,Mod);
for(rg int i=1;i<=Mod;i++) ny[i]=ksm(i,mod-2);
for(rg int i=2;i<=Mod;i++) pre(i);
ans=cx(rt[n],1,Mod,n-k+1,n);
printf("%d\n",ans);
for(rg int i=1;i<q;i++) C[i]=read();
for(rg int i=1;i<q;i++) D[i]=read();
for(rg int i=1;i<q;i++){
n=(1LL*A*ans+C[i])%Mod+1;
k=(1LL*B*ans+D[i])%n+1;
ans=cx(rt[n],1,Mod,n-k+1,n);
printf("%d\n",ans);
}
return 0;
}
C. 点点的最大流
分析
因为每一个点只会出现在一个简单环里
所以图一定是一个仙人掌
对于图上的某一个环,它最小边的流量一定是跑满的
所以我们可以把环上的最小边断掉,把这个权值加到环上的其它边上
这样原图就会变成一棵树的形态
我们只需要查询两点之间路径上的最小值就行了
因为环上的最小边是动态变化的,所以要用 \(lct\) 维护
剩下的就是大力分类讨论了
要注意最小值要开 \(2e9\) 以上,因为会有边权相加的情况
代码
数据生成器
#include <bits/stdc++.h>
using namespace std;
const int maxn=20;//It should be bigger than 4
const int maxm=20;//It should be bigger than maxn-2 and less than maxn*(maxn-1)/2+1
const int maxq=20;
const int maxval=10;
int vis[maxn+10][maxn+10];
int f[maxn+10];
int h[maxn+10];
int find(int x){
return x==f[x]?x:(f[x]=find(f[x]));
}
bool merge(int x,int y){
x=find(x);
y=find(y);
if(x==y&&!h[x]){
h[x]=1;
return 1;
}
else if(x==y)
return 0;
h[y]|=h[x];
f[x]=y;
return 1;
}
int main(){
freopen("/dev/urandom","r",stdin);
srand(getchar()*getchar()*getchar()*time(0));
freopen("data.in","w",stdout);
L:;
for(int i=0;i<maxn;i++)
for(int j=0;j<maxn;j++)
vis[i][j]=0;
for(int i=0;i<maxn;i++){
f[i]=i;
h[i]=0;
}
int n=rand()%(maxn-4)+5,m=n+rand()%(maxm-maxn+1);
if(n*(n-1)/2<m)
goto L;
for(int i=2;i<=m+1;i++){
int x,y;
if(i<=n){
x=i;
y=rand()%(i-1)+1;
}
else{
x=rand()%n+1;
y=rand()%n+1;
while(x==y||vis[x][y]){
x=rand()%n+1;
y=rand()%n+1;
}
}
vis[x][y]=vis[y][x]=1;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(vis[i][j])
if(!merge(i,j))
goto L;
printf("%d %d\n",n,m);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(vis[i][j])
printf("%d %d %d\n",i,j,rand()%maxval+1);
int q=rand()%maxq+1;
printf("%d\n",q);
for(int i=1;i<=q;i++){
int opt=rand()%2;
if(opt==0){
int x=rand()%n+1;
int y=rand()%n+1;
while(opt==0&&x==y){
x=rand()%n+1;
y=rand()%n+1;
}
printf("%d %d %d\n",opt,x,y);
}
else
printf("%d %d %d\n",opt,rand()%m+1,rand()%maxval+1);
}
return 0;
}
AC代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<map>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=4e5+5;
#define Min(a,b) (a<b?a:b)
struct LCT{
int fa[maxn],ch[maxn][2],rev[maxn],stk[maxn],tp,laz[maxn],wz[maxn];
long long mmin[maxn],val[maxn];
LCT(){
memset(mmin,0x3f,sizeof(mmin));
memset(val,0x3f,sizeof(val));
}
void push_up(rg int da){
mmin[da]=Min(val[da],Min(mmin[ch[da][0]],mmin[ch[da][1]]));
if(mmin[da]==val[da]) wz[da]=da;
else if(mmin[da]==mmin[ch[da][0]]) wz[da]=wz[ch[da][0]];
else wz[da]=wz[ch[da][1]];
}
void updat(rg int da,rg int nval){
laz[da]+=nval;
mmin[da]+=nval;
val[da]+=nval;
}
void push_down(rg int da){
rg int lc=ch[da][0],rc=ch[da][1];
if(laz[da]){
if(lc) updat(lc,laz[da]);
if(rc) updat(rc,laz[da]);
laz[da]=0;
}
if(rev[da]){
rev[lc]^=1,rev[rc]^=1,rev[da]^=1;
std::swap(ch[da][0],ch[da][1]);
}
}
bool isroot(rg int da){
return (ch[fa[da]][0]!=da)&&(ch[fa[da]][1]!=da);
}
void xuanzh(rg int x){
rg int y=fa[x];
rg int z=fa[y];
rg int k=(ch[y][1]==x);
if(!isroot(y)) ch[z][ch[z][1]==y]=x;
fa[x]=z;
ch[y][k]=ch[x][k^1];
fa[ch[x][k^1]]=y;
ch[x][k^1]=y;
fa[y]=x;
push_up(y);
push_up(x);
}
void splay(rg int x){
stk[tp=1]=x;
for(rg int i=x;!isroot(i);i=fa[i]) stk[++tp]=fa[i];
for(rg int i=tp;i>=1;i--) push_down(stk[i]);
while(!isroot(x)){
rg int y=fa[x];
rg int z=fa[y];
if(!isroot(y)) (ch[z][1]==y)^(ch[y][1]==x)?xuanzh(x):xuanzh(y);
xuanzh(x);
}
}
void access(rg int x){
for(rg int y=0;x;y=x,x=fa[x]){
splay(x);
ch[x][1]=y;
push_up(x);
}
}
void makeroot(rg int x){
access(x);
splay(x);
rev[x]^=1;
push_down(x);
}
void split(rg int x,rg int y){
makeroot(x);
access(y);
splay(y);
}
void link(rg int x,rg int y){
makeroot(x);
fa[x]=y;
}
void cut(rg int x,rg int y){
split(x,y);
ch[y][0]=fa[x]=0;
}
}lct;
int n,m,q,h[maxn],tot=2,dfn[maxn],dfnc,shuyu[maxn],low[maxn],sta[maxn],top,scc;
struct asd{
int to,nxt;
}b[maxn];
void ad(rg int aa,rg int bb){
b[tot].to=bb;
b[tot].nxt=h[aa];
h[aa]=tot++;
}
int zb[maxn],yb[maxn],v[maxn],rk[maxn],jud[maxn],haha[maxn];
std::map<std::pair<int,int>,int> mp;
std::vector<int> g[maxn];
void tar(rg int now,rg int lat){
low[now]=dfn[now]=++dfnc;
sta[++top]=now;
rg int tmp=0,jl=0,cs=0;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(!dfn[u]){
tar(u,now);
low[now]=Min(low[now],low[u]);
if(dfn[now]<=low[u]){
jl=now,scc++;
while(1){
cs=sta[top--];
tmp=mp[std::make_pair(jl,cs)];
if(!haha[tmp])g[scc].push_back(tmp);
haha[tmp]=1,jl=cs;
if(cs==u) break;
}
tmp=mp[std::make_pair(u,now)];
if(!haha[tmp])g[scc].push_back(tmp);
haha[tmp]=1;
}
} else if(u!=lat){
low[now]=Min(low[now],dfn[u]);
}
}
}//找环以及环上的最小边
bool cmp(rg int aa,rg int bb){
return v[aa]<v[bb];
}
int main(){
memset(h,-1,sizeof(h));
n=read(),m=read();
for(rg int i=1;i<=m;i++){
zb[i]=read(),yb[i]=read(),v[i]=read();
ad(zb[i],yb[i]);
ad(yb[i],zb[i]);
mp[std::make_pair(zb[i],yb[i])]=i;
mp[std::make_pair(yb[i],zb[i])]=i;
}
for(rg int i=1;i<=n;i++){
if(!dfn[i]) tar(i,0);
}
for(rg int i=1;i<=m;i++) lct.val[i+n]=v[i];
rg int aa,bb,cc,dd;
for(rg int i=1;i<=scc;i++){
std::sort(g[i].begin(),g[i].end(),cmp);
if(g[i].size()==1){
aa=g[i][0];
lct.link(zb[aa],aa+n),lct.link(yb[aa],aa+n);
shuyu[aa]=i;
} else {
for(rg int j=1;j<g[i].size();j++){
aa=g[i][j];
lct.link(zb[aa],aa+n),lct.link(yb[aa],aa+n);
shuyu[aa]=i;
}
aa=g[i][0],jud[aa]=1,shuyu[aa]=i,rk[i]=aa;
lct.split(zb[aa],yb[aa]);
lct.updat(yb[aa],v[aa]);
}
}//把不是环上最小边的所有边加到lct中,并记录一下最小边
q=read();
for(rg int i=1;i<=q;i++){
aa=read(),bb=read(),cc=read();
if(aa==0){
lct.split(bb,cc);
printf("%lld\n",lct.mmin[cc]);
} else {
if(g[shuyu[bb]].size()==1){
lct.split(zb[bb],yb[bb]);
lct.val[bb+n]=lct.mmin[bb+n]=cc;//如果只有一条边,分裂出来直接更新权值
} else {
if(jud[bb]){//如果当前边是最小边
lct.split(zb[bb],yb[bb]);
if(lct.mmin[yb[bb]]-v[bb]>=cc){//改变权值之后仍是最小边
lct.updat(yb[bb],cc-v[bb]);//把新的权值加上
v[bb]=cc;
lct.val[bb+n]=lct.mmin[bb+n]=v[bb];//更改最小边权值
} else {//最小边改变
dd=lct.wz[yb[bb]]-n;//找出最小的边
lct.updat(yb[bb],-v[bb]);//把之前的贡献删除
lct.mmin[bb+n]=lct.val[bb+n]=cc;//更新之前最小边的贡献
lct.cut(zb[dd],dd+n),lct.cut(yb[dd],dd+n);
lct.link(zb[bb],bb+n),lct.link(yb[bb],bb+n);//把原来的最小边接进去,新的最小边拿出来
lct.split(zb[dd],yb[dd]);
lct.updat(yb[dd],lct.val[dd+n]);//把新的最小边的贡献加上
jud[bb]=0,jud[dd]=1;
v[bb]=0,v[dd]=lct.val[dd+n];
rk[shuyu[bb]]=dd;;//更新记录最小边的数组
}
} else {
dd=rk[shuyu[bb]];
lct.split(zb[dd],yb[dd]);
lct.updat(yb[dd],-v[dd]);//找到最小边,并把最小边的贡献删除
if(cc<v[dd]){//如果新改变的边变成了最小边
lct.cut(zb[bb],bb+n),lct.cut(yb[bb],bb+n);
lct.link(zb[dd],dd+n),lct.link(yb[dd],dd+n);//把原来的最小边接进去,新的最小边拿出来
lct.split(zb[bb],yb[bb]);
lct.updat(yb[bb],cc);//把新的最小边的贡献加上
jud[dd]=0,jud[bb]=1;
v[dd]=0,v[bb]=cc;
rk[shuyu[bb]]=bb;//更新记录最小边的数组
lct.val[bb+n]=lct.mmin[bb+n]=v[bb];//改变最小边的权值
} else {
lct.split(zb[bb],yb[bb]);
lct.val[bb+n]=lct.mmin[bb+n]=cc;
lct.split(zb[dd],yb[dd]);
lct.updat(yb[dd],v[dd]);//如果没有影响,改改权值就可以了
}
}
}
}
}
return 0;
}