好吧,考得时候感觉第二题比较简单就直接切了(看到大家都瞄准第一题狠打的样子真吓坏了。。)
结果160第一收场。。。
第一题 追捕盗贼
题目大意:
给定无向连通图(无重边)
两种询问:
1、删除a、b间的边(直接相连的边)后,c能否到d
2、删除点a后,c能否到d
(设定:以下我已经用tarjan构成一颗树,并求出deep,low,dfn值)
那么我们可以用tarjan中的low和dfn来判定。
对于1,若删除a、b,c能到d,有几种情况:
一、a、b在一个环上,那么就有(设deep[a]<deep[b])也就是tarjan里a是b的前继,有 low[b]≤dfn[a]
二、a、b不在c到d的路径上,这个可以用lca判,a、b不在c到lca(c、d)到d的路径上(链剖dfs序判断)
(注意删除的边就是连着c或d或都连着)
可证明这是正确的(提示:反证法)
2.若删除a,c能到d,则有:
一、a不在c到d的路径上
二、设e和f,e为c的lca、lca(c、d)的最近子节点,f为d的lca、lca(c、d)的最近子节点,a=lca(c,d)(反过来也可),有max(low[e],low[f])<dfn[a],若只有c或d为e的子节点(设为c,e仍存在),则有:low[e]<dfn[a]
特判a=c或a=d
贴代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 100001
#define E 500001
using namespace std;
int q,n,m;
int stack[N][2],a[E*2][2],g[N],deep[N],dfn[N],low[N],fa[N][18];
bool bz[N],iscut[N],ans[2];
void ins(int x,int y){
static int sum=0;
a[++sum][0]=y,a[sum][1]=g[x],g[x]=sum;
}
void init(){
static int x,y;
scanf("%d %d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
}
void tarjan(int x){
static int sum=0,y;
low[x]=dfn[x]=++sum;
deep[x]++;
for (int i=0;y=fa[fa[x][i]][i];fa[x][++i]=y);
for (int i=g[x];i;i=a[i][1])
if (fa[x][0]!=a[i][0]){
int v=a[i][0];
if (!dfn[v]){
deep[v]=deep[x],fa[v][0]=x;
tarjan(v);
low[x]=min(low[x],low[v]);
}else
low[x]=min(low[x],dfn[v]);
}
deep[x]--;
}
void pre(){
deep[1]=1;
tarjan(1);
}
int getlca(int x,int y){
static int i;
i=17;
if (deep[x]<deep[y])swap(x,y);
while (deep[x]!=deep[y]){
for (;deep[fa[x][i]]<deep[y];i--);
x=fa[x][i];
}
i=17;
while (x!=y){
for (;fa[x][i]==fa[y][i]&&i;i--);
x=fa[x][i],y=fa[y][i];
}
return x;
}
int up(int x,int y){
static int i;
i=17;
if (deep[y]>=deep[x])return 0;
while (!(deep[x]-1==deep[y])){
for (;deep[fa[x][i]]<=deep[y];i--);
x=fa[x][i];
}
return fa[x][0]==y?low[x]:0;
}
void work(){
static int x,y,s,t,lca;
static bool e1,e2;
scanf("%d",&q);
for (;q--;){
ans[0]=ans[1]=0;
scanf("%d %d %d",&x,&s,&t);
lca=getlca(s,t);
if (x==1){
scanf("%d %d",&x,&y);
if (deep[x]<deep[y])swap(x,y);
if (deep[s]<deep[t])swap(s,t);
if (s==x&&t==y){
if (low[x]<=dfn[y])printf("yes\n");
else
printf("no\n");
}else
if (low[x]<=dfn[y]||deep[x]<deep[lca]||deep[y]<deep[lca])printf("yes\n");
else{
e1=(up(s,x)||up(t,x)||t==x||s==x);
e2=(up(s,y)||up(t,y)||t==y||s==y);
if (e1&&e2)printf("no\n");else printf("yes\n");
}
}else{
scanf("%d",&x);
if (deep[lca]>deep[x])printf("yes\n");
else
if (max(up(s,x),up(t,x))<dfn[x])printf("yes\n");
else
printf("no\n");
}
}
}
int main(){
init();
pre();
work();
return 0;
}
第二题 道路重组
题目大意
N个点N-1条无向边的组成的连通图,现在允许你删除一条边,再添加一条边,在保证连通的前提下,使得最远的两个点之间的距离尽可能小。
由于有诸多判定,我交了四次…最后终于过了(考的时候,虽说当时我没对拍,不知道…其实是没想到暴力…)
最远点,我们可以想到是树的直径,
那么删边我们可以看成将子树i,截出来,再将子树i与原树剩余部分的直径的中点连起来(这样可证为该决策下最优解,由于接起来不能改变原树剩余部分与子树i的直径,只能让通过他们连接的路径尽可能短)
那么我们可以用树形dp来做。
我们要维护子树最长、次长直径,子树最深、次深、次次深点到该子树根的路径,从前面搜来时记录的除该树外其余分支的最长直径、连到该子树的最长路径…看起来有点麻烦的说…但打起来时很有调理,所以还是可以打的。
贴代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 300001
int n,ans;
int g[N],a[N*2][2],b[N][3],from[N][3],len[N][2],back[N][2];
void ins(int x,int y){
static int sum=0;
a[++sum][0]=y,a[sum][1]=g[x],g[x]=sum;
}
void init(){
static int x,y;
scanf("%d",&n);
for (int i=1;i<n;i++)
scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
}
void did(int x,int y,int z){
if (len[x][0]<y){
len[x][1]=len[x][0],back[x][1]=back[x][0];
len[x][0]=y,back[x][0]=z;
}else
if (len[x][1]<y)
len[x][1]=y,back[x][1]=z;
}
void dfs(int x,int fa){
for (int i=g[x];i;i=a[i][1])
if (a[i][0]!=fa){
dfs(a[i][0],x);
static int y;
y=a[i][0];
if (b[y][0]+1>b[x][0]){
b[x][2]=b[x][1],from[x][2]=from[x][1];
b[x][1]=b[x][0],from[x][1]=from[x][0];
b[x][0]=b[y][0]+1,from[x][0]=y;
}else
if (b[y][0]+1>b[x][1]){
b[x][2]=b[x][1],from[x][2]=from[x][1];
b[x][1]=b[y][0]+1,from[x][1]=y;
}else
if (b[y][0]+1>b[x][2])
b[x][2]=b[y][0]+1,from[x][2]=y;
did(x,max(len[y][0],b[y][0]+b[y][1]+1),y);
}
}
void pre(){
dfs(1,0);
}
void treedp(int x,int y,int z,int fa){
static int s,s1;
s=max(len[x][0],b[x][0]+b[x][1]+1);
y=max(y,z);
if (x!=1)ans=min(ans,max(s,max(y,y/2+1+s/2+1)));
z++;
for (int i=g[x];i;i=a[i][1])
if (a[i][0]!=fa){
static bool e1,e2,e3;
e1=(a[i][0]!=from[x][0]&&from[x][0]),e2=(a[i][0]!=from[x][1]&&from[x][1]),e3=(a[i][0]!=from[x][2]&&from[x][2]);
if (back[x][0]==a[i][0])s=len[x][1];else s=len[x][0];
if (e1&&e2)
treedp(a[i][0],max(s,max(y,max(z,b[x][1]+1)+b[x][0])),max(z,b[x][0]+1),x);
else
if (e1&&e3)
treedp(a[i][0],max(s,max(y,max(z,b[x][2]+1)+b[x][0])),max(z,b[x][0]+1),x);
else
if (e2&&e3)
treedp(a[i][0],max(s,max(y,max(z,b[x][2]+1)+b[x][1])),max(z,b[x][1]+1),x);
else
if (!e1&&!e2&&!e3)
treedp(a[i][0],y,z,x);
else{
if (e1)s1=0;else if(e2)s1=1;else s1=2;
treedp(a[i][0],max(s,max(y,z+b[x][s1])),max(z,b[x][s1]+1),x);
}
}
}
void work(){
ans=b[1][0]+b[1][1]+1;
treedp(1,0,0,0);
}
void write(){
printf("%d",ans-1);
}
int main(){
init();
pre();
work();
write();
return 0;
}
第三题 阿凡达
题目大意
对数列a进行两种操作:
1 L R A B:对于编号为L到R之间的每一个数,假设编号为X,重新设置第X个数为(X-L+1)*A mod B,即第L个数设置为A mod B,第L+1个数设置为2*A mod B,依次类推下去;
2 L R:输出数列中第L个数到第R个数的和。
暴力30…
题目最大难点在于如何求:
∑
i*A%B
转化一下可得:
=
∑
i*A-
⌊i∗A/B⌋
*B
那么变成只求
∑⌊i∗A/B⌋
对于这个我们可以设成get(N,A。B,C),表示:
∑N0⌊(i∗A+C)/B⌋
分三种情况:
1、A≥B,原式=
∑N0⌊A/B⌋
*i+
∑N0⌊C/B⌋
+
∑N0⌊
(A%B*i+C%B)/B
⌋
=A/B*(n+1)N/2+C/B(N+1)+get(N,A%B,B,C%B)
2、A<B
证明:设Y=
⌊(A∗i+C)/B⌋
∑Ni=0∑Y−1j=0(j<Y)=>∑Y−1j=0∑Ni=0(j<Y)=>∑Y−1j=0N−⌊(j∗B+B−C−1)/A⌋=>Y∗N−get(Y−1,B,A,B−C−1)
中间的证明完善:
考虑何时(j<Y)
j+1≤(A*i+C)/B
j*B+B≤A*i+C
(j*B+B-B)/A≤i
注意除法为下取整,不能换位,加上线段树区间复制与查询
贴代码,其余细节处理好就行了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#define M 50001
using namespace std;
int n,m,top;
int a[M][5],b[M+M],c[M*4][2];
struct node{
__int128 A,B,L,v;
}f[M*16];
void init(){
scanf("%d %d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d %d %d",&a[i][0],&a[i][1],&a[i][2]);
if (a[i][0]==1)scanf("%d %d",&a[i][3],&a[i][4]);
b[++b[0]]=a[i][1],b[++b[0]]=a[i][2];
}
}
void pre(){
static int sum;
sort(b+1,b+b[0]+1);
sum=b[0];
b[0]=1;
for (int i=2;i<=sum;i++)
if (b[i]!=b[b[0]])b[++b[0]]=b[i];
for (int i=1;i<b[0];i++){
c[++top][0]=b[i],c[top][1]=b[i],c[++top][0]=b[i]+1,c[top][1]=b[i+1]-1;
if (c[top][0]>c[top][1])top--;
}
c[++top][0]=b[b[0]],c[top][1]=b[b[0]];
}
__int128 get(__int128 N,__int128 A,__int128 B,__int128 C){
if (!A)
return C/B*N;
if (A>=B)
return A/B*(N+1)*N/2+C/B*(N+1)+get(N,A%B,B,C%B);
else
return ((A*N+C)/B)*N-get((A*N+C)/B-1,B,A,B-C-1);
}
void down(int ll,int rr,int s){
static int ss,l,r;
if (f[s].A){
l=c[ll][0],r=c[rr][1];
ss=r-l+1;
f[s].v=f[s].A*(ss+f[s].L+f[s].L+1)*ss/2-(get(ss+f[s].L,f[s].A,f[s].B,0)-get(f[s].L,f[s].A,f[s].B,0))*f[s].B;
if (ll!=rr)
f[s+s]=f[s+s+1]=f[s],f[s+s+1].L+=c[(ll+rr)/2+1][0]-l;
f[s].A=0;
}
}
__int128 find(int l,int r,int s,int ll,int rr){
down(l,r,s);
if (c[r][1]<ll||rr<c[l][0])return 0;
if (ll<=c[l][0]&&c[r][1]<=rr)return f[s].v;
__int128 sum=find(l,(l+r)/2,s+s,ll,rr)+find((l+r)/2+1,r,s+s+1,ll,rr);
return sum;
}
void ins(int l,int r,int s,int ll,int rr,int x,int y){
down(l,r,s);
if (c[r][1]<ll||rr<c[l][0])return;
if (ll<=c[l][0]&&c[r][1]<=rr){
f[s].A=x,f[s].B=y,f[s].L=c[l][0]-ll;
down(l,r,s);
return;
}
ins(l,(l+r)/2,s+s,ll,rr,x,y),ins((l+r)/2+1,r,s+s+1,ll,rr,x,y);
f[s].v=f[s+s].v+f[s+s+1].v;
}
void work(){
for (int i=1;i<=m;i++)
if (a[i][0]==1)
ins(1,top,1,a[i][1],a[i][2],a[i][3],a[i][4]);
else{
long long ans;
ans=find(1,top,1,a[i][1],a[i][2]);
printf("%lld\n",ans);
}
}
int main(){
init();
pre();
work();
return 0;
}