A. Candy Game
显然最优策略是一个一个吃,故比较哪种糖果的个数比较多即可。
#include<cstdio>
int T,n,i,x,sum;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
sum=0;
for(i=1;i<=n;i++)scanf("%d",&x),sum+=x;
for(i=1;i<=n;i++)scanf("%d",&x),sum-=x;
puts(sum>0?"BaoBao":"DreamGrid");
}
}
B. PreSuffix
对所有串建立AC自动机,那么若前缀$i$是前缀$j$的后缀,说明$i$是Fail树上$j$的祖先。
所以对于询问$(x,y)$,答案就是两点在Fail树上的LCA在原Trie中子树内的字符串总数。
时间复杂度$O(n\log n)$。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010,M=500010;
char s[M];
int tot,son[M][26],fail[M],q[M],val[M],G[M],NXT[M],size[M],heavy[M],top[M],d[M];
int n,i,j,m,x,y,pos[N];
inline void ins(int p){
int l=strlen(s),x=0;
for(int i=0,w;i<l;i++){
if(!son[x][w=s[i]-'a'])son[x][w]=++tot;
x=son[x][w];
val[x]++;
}
pos[p]=x;
}
void make(){
int h=1,t=0,i,j,x;fail[0]=-1;
for(i=0;i<26;i++)if(son[0][i])q[++t]=son[0][i];
while(h<=t)for(x=q[h++],i=0;i<26;i++)
if(son[x][i])fail[son[x][i]]=son[fail[x]][i],q[++t]=son[x][i];
else son[x][i]=son[fail[x]][i];
}
void dfs(int x){
size[x]=1;
for(int i=G[x];i;i=NXT[i]){
d[i]=d[x]+1;
dfs(i);
size[x]+=size[i];
if(heavy[x]<0)heavy[x]=i;
else if(size[i]>size[heavy[x]])heavy[x]=i;
}
}
void dfs2(int x,int y){
top[x]=y;
if(~heavy[x])dfs2(heavy[x],y);
for(int i=G[x];i;i=NXT[i])if(i!=heavy[x])dfs2(i,i);
}
inline int lca(int x,int y){
for(;top[x]!=top[y];x=fail[top[x]])if(d[top[x]]<d[top[y]])swap(x,y);
return d[x]<d[y]?x:y;
}
inline void ask(int x,int y){
x=pos[x];
y=pos[y];
int z=lca(x,y);
if(!val[z])puts("N");else printf("%d\n",val[z]);
}
int main(){
while(~scanf("%d",&n)){
for(i=1;i<=n;i++)scanf("%s",s),ins(i);
make();
for(i=1;i<=tot;i++)NXT[i]=G[fail[i]],G[fail[i]]=i;
for(i=0;i<=tot;i++)heavy[i]=-1;
dfs(0);
dfs2(0,0);
scanf("%d",&m);
while(m--)scanf("%d%d",&x,&y),ask(x,y);
for(i=0;i<=tot;i++){
for(fail[i]=G[i]=j=0;j<26;j++)son[i][j]=0;
val[i]=size[i]=top[i]=d[i]=0;
heavy[i]=-1;
}
tot=0;
}
return 0;
}
C. An Unsure Catch
考虑$k=0$的情形:
对于每个连通块,断开一条环边变成树,求出树上每个点的深度$d$。
设环长为$len$,则该连通块中最优解为$d\bmod len$中出现次数最多的那一个。
考虑$k>0$的情形,有两种最优策略:
$1.$ 利用一步操作将某个环长修改为$1$,再用$k-1$步操作把其它$k-1$个连通块合并上来。
故此时答案为连通块点数最大的$k$个的点数之和。
$2.$ 不刻意制造长度为$1$的环,直接用$k$步操作把$k-1$个连通块合并到某个点上。
枚举每个连通块作为最终合并点,假设其环长为$K$,则要选$k-1$个其它连通块,将它们各断开一条边,然后将环长修改为$K$,使得$d\bmod K$中出现次数最大值最大。
注意到$K$的取值只有$O(\sqrt{n})$种,枚举每个$K$,对于每个连通块计算最优解,然后计数排序统计前$k$大值的和即可。
对于每个连通块,假设环长为$len$,那么按顺序依次断开每条环边后,对断开点子树内所有$d$的影响是整体减去$len$,共$O(n)$次修改和$O(n)$次查询。
注意到每次修改时,只会将某个数加$1$或减$1$,故最大值也只会变化$1$,记录每种值有多少个即可判断最大值是否需要变化,时间复杂度$O(1)$。
总时间复杂度$O(n\sqrt{n})$。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=100010,inf=100000000;
int Case,n,i,a[N],g[N],nxt[N];
bool vis[N],visit[N],on[N];
int d[N];
int f[N];
int id[N],m,ce;
int fin[N];
int vs[N];
bool need[N];
int len[N],head[N],cur;
P pool[N];
int nowmx,cf[N],have[N];
inline void add(int x,int y){nxt[y]=g[x];g[x]=y;}
void dfs(int x){
if(vis[x])return;
vis[x]=1;
id[++m]=x;
for(int i=g[x];i;i=nxt[i])dfs(i);
dfs(a[x]);
}
int cal(int x){
if(d[x])return d[x];
return d[x]=cal(a[x])+1;
}
inline void solve(){
int x=id[1];
while(!visit[x])visit[x]=1,x=a[x];
int circle=0;
while(!on[x]){
circle++;
d[x]=inf-circle;
on[x]=1,x=a[x];
}
ce++;
vs[ce]=m;
need[circle]=1;
len[ce]=circle;
head[ce]=x;
for(int i=1;i<=m;i++)pool[++cur]=P(ce,cal(id[i]));
}
inline void up(int&a,int b){a<b?(a=b):0;}
inline void addf(int x){
cf[++f[x]]++;
up(nowmx,f[x]);
}
inline void delf(int x){
cf[f[x]--]--;
if(!cf[nowmx])nowmx--;
}
int MO,L;
void update(int x){
delf(d[x]%MO);
addf((d[x]-L)%MO);
for(int i=g[x];i;i=nxt[i])if(!on[i])update(i);
}
inline void mustlen(int K){
MO=K;
int i,j,k=0,x;
int hismax=0;
for(i=1;i<=n+1;i++)have[i]=0;
for(i=1;i<=n;i=j){
for(j=i;j<=n&&pool[i].first==pool[j].first;j++);
int l=i,r=j-1;
k++;
nowmx=0;
L=len[k];
int best=0;
for(x=l;x<=r;x++)addf(pool[x].second%K);
best=nowmx;
int S=x=head[k];
while(1){
update(x);
up(best,nowmx);
x=a[x];
if(x==S)break;
}
for(x=l;x<=r;x++)delf((pool[x].second-L)%K);
have[best]++;
if(L==K&&best>hismax)hismax=best;
}
have[hismax]--;
up(fin[k=0],hismax);
for(i=n;i;i--)for(j=have[i];j;j--)up(fin[++k],hismax+=i);
for(k++;k<=n;k++)up(fin[k],hismax);
}
int main(){
scanf("%d",&Case);
cf[0]=1e9;
while(Case--){
scanf("%d",&n);
for(i=0;i<=n;i++)g[i]=vis[i]=visit[i]=on[i]=d[i]=f[i]=id[i]=need[i]=0;
m=ce=cur=0;
for(i=1;i<=n;i++)scanf("%d",&a[i]),add(a[i],i);
for(i=1;i<=n;i++)if(!vis[i])m=0,dfs(i),solve();
sort(vs+1,vs+ce+1);reverse(vs+1,vs+ce+1);
for(i=1;i<=ce;i++)vs[i]+=vs[i-1];
sort(pool+1,pool+cur+1);
for(i=0;i<=n;i++)fin[i]=vs[min(ce,i)];
for(i=1;i<=n;i++)if(need[i])mustlen(i);
int k=0;
for(i=0;i<=n;i++){
fin[i]=min(fin[i],n);
k=i;
if(fin[i]>=n)break;
}
printf("%d\n",k);
for(i=0;i<=k;i++)printf("%d%c",fin[i],i<k?' ':'\n');
}
}
D. Seat Assignment
$lcm(1,2,...,10)=2520$,对于模$lcm$的每个余数$k$分析其是否是$1$到$10$的倍数,可以发现一共$48$种本质不同的情况。
那么计算出每种情况的容量之后,就转化成了左边$10$个点,右边$48$个点的最大流。
#include<cstdio>
const int N=10000,inf=~0U>>2;
int lcm,m,i,j,Case,lim[N],q[N],mask[N],vis[N],cnt;
int S,T,h[N],gap[N],maxflow;
struct E{int t,f;E*nxt,*pair;}*g[N],*d[N],pool[100000],*cur=pool;
inline int min(int a,int b){return a<b?a:b;}
inline void add(int s,int t,int f){
E*p=cur++;p->t=t;p->f=f;p->nxt=g[s];g[s]=p;
p=cur++;p->t=s;p->f=0;p->nxt=g[t];g[t]=p;
g[s]->pair=g[t];g[t]->pair=g[s];
}
int sap(int v,int flow){
if(v==T)return flow;
int rec=0;
for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){
int ret=sap(p->t,min(flow-rec,p->f));
p->f-=ret;p->pair->f+=ret;d[v]=p;
if((rec+=ret)==flow)return flow;
}
if(!(--gap[h[v]]))h[S]=T;
gap[++h[v]]++;d[v]=g[v];
return rec;
}
int main(){
lcm=2520;
for(i=0;i<lcm;i++){
//k%lcm=i
int S=0;
for(j=1;j<=10;j++)if(i%j==0)S|=1<<j;
if(!vis[S]){
vis[S]=++cnt;
q[cnt]=S;
}
mask[i]=vis[S];
}
scanf("%d",&Case);
while(Case--){
scanf("%d",&m);
for(i=1;i<=cnt;i++)lim[i]=0;
int sum=0;
for(i=0;i<lcm;i++){
int now=m-i;
if(now<0)now=0;
now=now/lcm;
if(i&&i<=m)now++;
lim[mask[i]]+=now;
}
S=cnt+11;
T=S+1;
for(cur=pool,i=1;i<=T;i++)g[i]=d[i]=NULL,h[i]=gap[i]=0;
for(i=1;i<=cnt;i++)add(S,i,lim[i]);
for(i=1;i<=10;i++){
int x;
scanf("%d",&x);
add(cnt+i,T,x);
if(x)for(j=1;j<=cnt;j++)if(q[j]>>i&1)add(j,cnt+i,x);
}
for(gap[maxflow=0]=T,i=1;i<=T;i++)d[i]=g[i];
while(h[S]<T)maxflow+=sap(S,inf);
printf("%d\n",maxflow);
}
}
/*
2^10
%1=0
%2=
1..m
%lcm
lcm=8*9*5*7
*/
E. Yet Another Data Structure Problem
线段树维护序列乘积$mul$,标记$(a,b)$表示$mul=a\times mul^b$。
时间复杂度$O(n\log n\log P)$。
#include<cstdio>
const int N=100010,M=262150,P=1000000007;
int Case,n,m,i,a[N],ans;
int v[M],ta[M],tb[M],len[M];
inline int po(int a,int b){
int t=1;
for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P;
return t;
}
inline void tag1(int x,int a,int b){
v[x]=1LL*po(v[x],b)*po(a,len[x])%P;
ta[x]=1LL*po(ta[x],b)*a%P;
tb[x]=1LL*tb[x]*b%(P-1);
}
inline void pb(int x){
if(ta[x]==1&&tb[x]==1)return;
tag1(x<<1,ta[x],tb[x]);
tag1(x<<1|1,ta[x],tb[x]);
ta[x]=tb[x]=1;
}
inline void up(int x){v[x]=1LL*v[x<<1]*v[x<<1|1]%P;}
void build(int x,int a,int b){
ta[x]=tb[x]=1;
len[x]=b-a+1;
if(a==b){v[x]=::a[a];return;}
int mid=(a+b)>>1;
build(x<<1,a,mid),build(x<<1|1,mid+1,b);
up(x);
}
void change(int x,int a,int b,int c,int d,int A,int B){
if(c<=a&&b<=d){tag1(x,A,B);return;}
pb(x);
int mid=(a+b)>>1;
if(c<=mid)change(x<<1,a,mid,c,d,A,B);
if(d>mid)change(x<<1|1,mid+1,b,c,d,A,B);
up(x);
}
void ask(int x,int a,int b,int c,int d){
if(c<=a&&b<=d){ans=1LL*ans*v[x]%P;return;}
pb(x);
int mid=(a+b)>>1;
if(c<=mid)ask(x<<1,a,mid,c,d);
if(d>mid)ask(x<<1|1,mid+1,b,c,d);
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
build(1,1,n);
while(m--){
int op,l,r,k;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&k);
change(1,1,n,l,r,k,1);
}
if(op==2){
scanf("%d",&k);
change(1,1,n,l,r,1,k);
}
if(op==3){
ans=1;
ask(1,1,n,l,r);
printf("%d\n",ans);
}
}
}
}
UPD:
$P=1000000007$的原根$g$为$5$,如果将每个数都取指标的话,则变为模$P-1$意义下的区间加、区间乘、区间求和,可以线段树$O(n\log n)$维护。
对于每个询问,求出区间指标之和$sum$后,该询问的答案$ans=g^{sum}\bmod P$。
注意到只需要预处理$1$到$1000$的指标,故更改BSGS算法中的步长为$300000$,可以降低每次求指标的时间复杂度。
#include<cstdio>
#include<algorithm>
#include<tr1/unordered_map>
using namespace std;
using namespace std::tr1;
const int N=100010,M=262150,P=1000000007,Q=P-1;
int Case,n,m,i,a[N],ans;
int v[M],ta[M],tb[M],len[M];
inline int po(int a,int b){
int t=1;
for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P;
return t;
}
namespace NT{
unordered_map<int,int>T;
typedef pair<int,int>PI;
const int K=300000,G=5;
int ind[1005],base,i;
inline int ask(int x){
if(x==1)return 0;
int t=po(x,P-2);
for(int i=K;;i+=K){
t=1LL*t*base%P;
unordered_map<int,int>::iterator it=T.find(t);
if(it!=T.end())return i-it->second;
}
}
void init(){
for(base=1,i=0;i<K;i++){
T.insert(PI(base,i));
base=1LL*base*G%P;
}
for(i=1;i<=1000;i++)ind[i]=ask(i);
}
}
inline void tag1(int x,int a,int b){
v[x]=(1LL*v[x]*b+1LL*a*len[x])%Q;
tb[x]=1LL*tb[x]*b%Q;
ta[x]=(1LL*ta[x]*b+a)%Q;
}
inline void pb(int x){
if(ta[x]==0&&tb[x]==1)return;
tag1(x<<1,ta[x],tb[x]);
tag1(x<<1|1,ta[x],tb[x]);
ta[x]=0,tb[x]=1;
}
inline void up(int x){v[x]=(v[x<<1]+v[x<<1|1])%Q;}
void build(int x,int a,int b){
ta[x]=0,tb[x]=1;
len[x]=b-a+1;
if(a==b){v[x]=::a[a];return;}
int mid=(a+b)>>1;
build(x<<1,a,mid),build(x<<1|1,mid+1,b);
up(x);
}
void change(int x,int a,int b,int c,int d,int A,int B){
if(c<=a&&b<=d){tag1(x,A,B);return;}
pb(x);
int mid=(a+b)>>1;
if(c<=mid)change(x<<1,a,mid,c,d,A,B);
if(d>mid)change(x<<1|1,mid+1,b,c,d,A,B);
up(x);
}
void ask(int x,int a,int b,int c,int d){
if(c<=a&&b<=d){ans=(ans+v[x])%Q;return;}
pb(x);
int mid=(a+b)>>1;
if(c<=mid)ask(x<<1,a,mid,c,d);
if(d>mid)ask(x<<1|1,mid+1,b,c,d);
}
int main(){
NT::init();
scanf("%d",&Case);
while(Case--){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i]=NT::ind[a[i]];
build(1,1,n);
while(m--){
int op,l,r,k;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&k);
change(1,1,n,l,r,NT::ind[k],1);
}
if(op==2){
scanf("%d",&k);
change(1,1,n,l,r,0,k);
}
if(op==3){
ans=0;
ask(1,1,n,l,r);
printf("%d\n",po(NT::G,ans));
}
}
}
}
F. The Limit
如果分子分母不同时为$0$,那么极限显然,否则根据洛必达法则分别求导计算。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=15;
char s[100000];
ll X;
struct Poly{
ll v[N];
Poly(){memset(v,0,sizeof v);}
void read(){
memset(v,0,sizeof v);
scanf("%s",s);
int len=strlen(s);
int i,j;
for(i=0;i<len;){
if(s[i]=='x'){
if(s[i+1]=='^'){
v[s[i+2]-'0']++;
i+=3;
continue;
}
v[1]++;
i++;
continue;
}
if(s[i]=='+'||s[i]=='-'){
int flag=s[i]=='+'?1:-1;
if(s[i+1]=='x'){
if(s[i+2]=='^'){
v[s[i+3]-'0']+=flag;
i+=4;
continue;
}
v[1]+=flag;
i+=2;
continue;
}else{
//is number
ll num=flag*(s[i+1]-'0');
if(s[i+2]=='x'){
if(s[i+3]=='^'){
v[s[i+4]-'0']+=num;
i+=5;
continue;
}
v[1]+=num;
i+=3;
continue;
}else{
v[0]+=num;
i+=2;
continue;
}
}
}
//is number
ll num=s[i]-'0';
if(s[i+1]=='x'){
if(s[i+2]=='^'){
v[s[i+3]-'0']+=num;
i+=4;
continue;
}
v[1]+=num;
i+=2;
continue;
}else{
v[0]+=num;
i++;
continue;
}
}
}
ll f(ll x){
ll ret=0,t=1;
for(int i=0;i<N;i++){
ret+=v[i]*t;
t*=x;
}
return ret;
}
void write(){
for(int i=0;i<N;i++)if(v[i])printf("%lldx^%d ",v[i],i);puts(".");
}
void dao(){
for(int i=1;i<N;i++)v[i-1]=v[i]*i;
v[N-1]=0;
}
}A,B;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
void solve(){
A.read();
B.read();
scanf("%lld",&X);
while(1){
ll U=A.f(X),D=B.f(X);
if(!U&&D){
puts("0");
return;
}
if(U&&!D){
puts("INF");
return;
}
if(U&&D){
ll d=gcd(abs(U),abs(D));
U/=d,D/=d;
if(D<0)U*=-1,D*=-1;
if(D!=1)printf("%lld/%lld\n",U,D);else printf("%lld\n",U);
return;
}
A.dao();
B.dao();
}
}
int main(){
int Case;
scanf("%d",&Case);
while(Case--)solve();
}
G. It's High Noon
假设人位于$(x,y)$,若攻击部分背对原点,那么显然最优情况下$(x,y)=(0,0)$。将所有点极角排序然后双指针枚举即可。注意特判点在原点的情况。
若攻击部分面向原点,那么显然最优情况下$x^2+y^2=R^2$,且直线与圆相切。抠除圆内部的所有点之后,对于剩下的点求出切线角度,那么角度在某个区间内的直线都能取到这个点,扫描线统计即可。
时间复杂度$O(n\log n)$。
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=500000;
const double pi=acos(-1.0),PI=acos(-1.0)*2.0,eps=1e-9;
int Case,n,K,i,ans;
struct P{
int x,y;
}a[N],b[N];
inline int pos(int x,int y){return x?x>0:y>0;}
inline int cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;}
inline bool cmp(const P&a,const P&b){
if(pos(a.x,a.y)!=pos(b.x,b.y))return pos(a.x,a.y)<pos(b.x,b.y);
return cross(a,b)<0;
}
struct E{
double x;int y;
E(){}
E(double _x,int _y){x=_x,y=_y;}
}e[N];
inline bool cmpe(const E&a,const E&b){return a.x<b.x;}
void solve_center(){
int atcenter=0;
int m=0;
int i,j;
for(i=1;i<=n;i++)if(!a[i].x&&!a[i].y)atcenter++;else{
b[++m]=a[i];
}
ans=max(ans,atcenter);
sort(b+1,b+m+1,cmp);
for(i=1;i<=m;i++)b[i+m]=b[i];
for(i=j=1;i<=m;i++){
if(j<i)j=i;
while(j+1<i+m&&cross(b[i],b[j+1])<=0)j++;
ans=max(ans,j-i+1+atcenter);
}
}
inline int sgn(double x){
if(x>eps)return 1;
if(x<-eps)return -1;
return 0;
}
inline void fix(double&x){
while(sgn(x)<0)x+=PI;
while(sgn(x-PI)>=0)x-=PI;
x=max(x,(double)0.0);
}
const double eps2=eps*5;
void solve_ka(){
int all=0;
int i,j,k;
int m=0;
for(i=1;i<=n;i++){
int dis=a[i].x*a[i].x+a[i].y*a[i].y;
if(dis>K){
double o=atan2(-a[i].y,-a[i].x);
double w=acos(sqrt(max(1.0-1.0*K/dis,0.0)));
w=fabs(w);
double A=o-w,B=o+w+pi;
double l=A,r=B;
l-=eps2,r+=eps2;
fix(l);
fix(r);
if(!sgn(l-r))r=l+eps;
//printf("%.10f %.10f\n",l,r);
if(l>r)all++;
//[l..r] 1 else 0
e[++m]=E(l,1);
e[++m]=E(r,-1);
}else all++;
}
ans=max(ans,all);
sort(e+1,e+m+1,cmpe);
for(i=1;i<=m;i++){
all+=e[i].y;
ans=max(ans,all);
}
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%d",&n,&K);K*=K;
for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
ans=0;
solve_center();
solve_ka();
printf("%d\n",ans);
}
}
H. Traveling Plan
对于每个点$x$求出$d_x$表示离它最近的补给站到它的距离。
对于每条边$(x,y,w)$,将边权重置为$d_x+d_y+w$。
那么对于询问$(x,y)$,答案就是最小生成树上两点间边权的最大值。
时间复杂度$O(n\log n)$。
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll,int>P;
const int N=100010,M=200010,K=20;
const ll inf=1LL<<60;
int n,m,q,x,y,i,j,f[N],g[M<<1],v[M<<1],nxt[M<<1],ed;ll w[M<<1];
int d[N],fa[N][K];
ll fw[N][K];
ll dis[N];
bool vis[N];
priority_queue<P,vector<P>,greater<P> >Q;
struct E{int x,y;ll z;}e[M];
inline bool cmp(const E&a,const E&b){return a.z<b.z;}
int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
inline void add(int x,int y,ll z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
void dfs(int x,int y){
vis[x]=1;
for(int i=1;i<K;i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
fw[x][i]=max(fw[x][i-1],fw[fa[x][i-1]][i-1]);
}
for(int i=g[x];i;i=nxt[i])if(v[i]!=y){
fa[v[i]][0]=x;
fw[v[i]][0]=w[i];
d[v[i]]=d[x]+1;
dfs(v[i],x);
}
}
inline ll ask(int x,int y){
ll ret=0;
if(d[x]<d[y])swap(x,y);
for(int i=K-1;~i;i--)if(d[fa[x][i]]>=d[y]){
ret=max(ret,fw[x][i]);
x=fa[x][i];
}
if(x==y)return ret;
for(int i=K-1;~i;i--)if(fa[x][i]!=fa[y][i]){
ret=max(ret,max(fw[x][i],fw[y][i]));
x=fa[x][i];
y=fa[y][i];
}
return max(ret,max(fw[x][0],fw[y][0]));
}
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",&x);
if(x==1){
Q.push(P(0,i));
}else{
dis[i]=inf;
}
}
for(i=1;i<=m;i++){
scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].z);
add(e[i].x,e[i].y,e[i].z);
add(e[i].y,e[i].x,e[i].z);
}
while(!Q.empty()){
P t=Q.top();Q.pop();
if(dis[t.second]<t.first)continue;
for(i=g[t.second];i;i=nxt[i])if(dis[v[i]]>t.first+w[i])Q.push(P(dis[v[i]]=t.first+w[i],v[i]));
}
for(i=1;i<=m;i++)e[i].z+=dis[e[i].x]+dis[e[i].y];
sort(e+1,e+m+1,cmp);
for(i=1;i<=n;i++)g[i]=0;
ed=0;
for(i=1;i<=n;i++)f[i]=i;
for(i=1;i<=m;i++)if(F(e[i].x)!=F(e[i].y)){
f[f[e[i].x]]=f[e[i].y];
add(e[i].x,e[i].y,e[i].z);
add(e[i].y,e[i].x,e[i].z);
}
for(i=1;i<=n;i++)if(!vis[i])dfs(i,0);
scanf("%d",&q);
while(q--)scanf("%d%d",&x,&y),printf("%lld\n",ask(x,y));
}
I. Wooden Bridge
留坑。
J. Distance
枚举$O(n^2)$对区间右端点,左端点的取值满足单调性,双指针即可。
时间复杂度$O(n^2)$。
#include<cstdio>
typedef long long ll;
const int N=1010;
int Case,n,p,i,j,k,ans,g[N*3];ll a[N],b[N],f[N][N],w[N*3],V;
inline ll cal(ll x){
if(x<0)x=-x;
ll t=1;
for(int i=0;i<p;i++)t*=x;
return t;
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%lld%d",&n,&V,&p);
for(i=1;i<=n;i++)scanf("%lld",&a[i]);
for(i=1;i<=n;i++)scanf("%lld",&b[i]);
ans=0;
for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=cal(a[i]-b[j]);
for(i=0;i<=n+n+5;i++)g[i]=w[i]=0;
for(i=1;i<=n;i++)for(j=1;j<=n;j++){
k=i-j+n;
g[k]++;
w[k]+=f[i][j];
int G=g[k];ll W=w[k];
while(G>0&&W>V){
G--;
W-=f[i-G][j-G];
}
g[k]=G;
w[k]=W;
ans+=G;
}
printf("%d\n",ans);
}
}