2023年10月16日 / 信息学 / 算法
题目见洛谷。利用每个点在树上距离最远的点一定是直径之一之一的性质,再使用倍增
求LCA
的方法就可以了。
细节不再赘述,自行看代码吧。
#include
using namespace std;
#define pii pair<int,int>
#define fi first
#define se second
const int maxn=2*1e5;
const int maxm=20;
int n,m;
struct Edge{int v,nxt;};
Edge e[maxn*2+5];
int hd[maxn+5],et;
pii pt[2];
int f[maxn+5][maxm+5];
int de[maxn+5];
int pw[maxm+5];
inline void Adde(int u,int v){e[et].v=v,e[et].nxt=hd[u],hd[u]=et++;}
pii Dfs(int u,int fa,bool flg)
{
int v;
pii mx=pii(0,u),tmp;
for(int i=hd[u];~i;i=e[i].nxt)
{
v=e[i].v;
if(v==fa) continue;
if(flg)
{
f[v][0]=u;
de[v]=de[u]+1;
}
tmp=Dfs(v,u,flg);
tmp.fi++;
mx=max(mx,tmp);
}
return mx;
}
inline void Init()
{
pw[0]=1;
for(int j=1;j<=maxm;j++)
{
pw[j]=pw[j-1]*2;
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
}
inline int Lca(int x,int y)
{
if(de[x]<de[y]) swap(x,y);
for(int i=maxm;i>=0;i--)
if(de[f[x][i]]>=de[y])
x=f[x][i];
for(int i=maxm;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
if(x==y) return x;
else return f[x][0];
}
inline int Dist(int x,int y){return de[x]-de[y];}
inline int Jump(int x,int v)
{
for(int i=maxm;i>=0;i--)
if(pw[i]<=v)
v-=pw[i],x=f[x][i];
return x;
}
signed main()
{
freopen("intoxicated.in","r",stdin);
freopen("intoxicated.out","w",stdout);
int u,v;
memset(e,-1,sizeof(e));
memset(hd,-1,sizeof(hd));
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
Adde(u,v);Adde(v,u);
}
pt[0]=Dfs(1,0,1);
pt[1]=Dfs(pt[0].se,0,0);
Init();
int tmp,t1,t2;
bool flg;
scanf("%d",&m);
while(m--)
{
flg=0;
scanf("%d%d",&u,&v);
for(int i=0;i<=1;i++)
{
tmp=Lca(u,pt[i].se);
t1=Dist(u,tmp);
t2=Dist(pt[i].se,tmp);
if(v<=t1)
{
flg=1;
printf("%d\n",Jump(u,v));
break;
}
else if(v<=t1+t2)
{
flg=1;
printf("%d\n",Jump(pt[i].se,t1+t2-v));
break;
}
}
if(!flg) printf("-1\n");
}
return 0;
}
题目见洛谷。看到按位操作,往二进制方向想。
我们先预处理 a [ i ] [ j ] a[i][j] a[i][j]表示第 i i i个数二进制的第 j j j位( 0 0 0开始)。
对于 a [ x ] , a [ y ] a[x],a[y] a[x],a[y]它们之间可以到达要存在一个 j j j使得 a [ x ] [ j ] = a [ y ] [ j ] = 1 a[x][j]=a[y][j]=1 a[x][j]=a[y][j]=1。下文我们把 j j j称为 ( x , y ) (x,y) (x,y)的路径。
考虑动态规划, g [ i ] [ j ] g[i][j] g[i][j]表示小于 i i i的位置可以与 a [ i ] a[i] a[i]接上并且第 j j j位为 1 1 1的最大位置。
那么对于询问 [ l , r ] [l,r] [l,r],只要判断是否存在 j j j使得 g [ r ] [ j ] ≥ l g[r][j] \geq l g[r][j]≥l即可。
怎么转移,分两种情况需要或不需要中转,枚举 k k k作为中转的路径。
此时仍需枚举 l l l,考虑再动态规划预处理 l l l。观察 l l l的条件不难发现 l l l需要满足 l , i l,i l,i之间有路径 k k k,并且因为 g g g递增,所以 l l l越大越好。
定义 f [ i ] [ j ] f[i][j] f[i][j]表示小于 i i i的第 j j j位为 1 1 1的最大点。那么有转移:
所有初始值都为 0 0 0即可解决这道题。
#include
using namespace std;
const int maxn=3*1e5;
const int maxs=20;
int n,m,q;
int a[maxn+5][maxs+5];
int st[maxs+5],stt;
int f[maxn+5][maxs+5],g[maxn+5][maxs+5];
signed main()
{
freopen("and.in","r",stdin);
freopen("and.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i][0]);
stt=0;
while(a[i][0]>0)
{
st[stt++]=a[i][0]&1;
a[i][0]>>=1;
}
for(int j=0;j<stt;j++)
a[i][j]=st[j];
m=max(m,stt);
}
for(int i=1;i<=n;i++)
for(int j=0;j<m;j++)
f[i][j]=a[i-1][j]?i-1:f[i-1][j];
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
for(int k=0;k<m;k++)
{
if(a[i][k])
{
g[i][j]=max(g[i][j],g[f[i][k]][j]);
if(a[f[i][k]][j]) g[i][j]=max(g[i][j],f[i][k]);
}
}
}
}
int x,y;
bool flg;
while(q--)
{
scanf("%d%d",&x,&y);
flg=0;
for(int i=0;i<m;i++)
{
if(a[x][i]&&g[y][i]>=x)
{
flg=1;
break;
}
}
printf("%s\n",flg?"Shi":"Fou");
}
return 0;
}
题目见洛谷。考虑分块,一个怪经过一座塔只有两种情况:
一个怪只能被击杀 m m m次,若依第二种情况只会出现 m m m次。
我们对于每一个块维护一个平推标记,并记录上一次操作的时间,被平推 i i i秒后造成的伤害,对于每个怪一次处理每个块。
分析一下,时间、空间复杂度为 O ( n n ) O(n\sqrt n) O(nn)级别左右。虽然时间没问题,但是空间并不允许。
我们考虑离线怪物,因为怪物依次经过块,所以只要对于每一个块枚举怪物就好,只需保存当前块的信息,空间降至 O ( n ) O(n) O(n)级别。
所以我们解决了这道题,当然代码中还有很多实现细节,可以参考注释。
代码常数很大,见谅。
#include
using namespace std;
#define int long long
const int maxn=4*1e6;
int sz;
int n,m;
int ans;
int tim;//操作时间
bool sta;//平推标记 注意:1表示没有平推
int sum[maxn+5];//平推若干秒后造成的伤害
int cnt[maxn+5],lst[maxn+5];//约等于临时变量,忽略
int c[maxn+5];//塔的血量
struct Tower{int mx,dl;};//塔的上限,恢复速度
Tower T[maxn+5];
struct Dino{int tim,hel;};//怪的出现时间,血量
Dino D[maxn+5];
struct Block{int id,l,r;};//分块编号,左右端点
Block B[maxn+5];
inline void Init()//初始化分块
{
sz=sqrt(n);
for(int i=1;i<=n;i++)
c[i]=T[i].mx;
for(int i=1;i<=n;i++)
{
B[i].id=(i-1)/sz+1;
B[i].l=(B[i].id-1)*sz+1;
B[i].r=min(B[i].id*sz,n);
}
}
inline void Work1(int x,int y)//暴力扫
{
int now=D[y].tim+B[x].l-1;//塔被攻击的时间
sta=0;
for(int i=B[x].l;i<=B[x].r;i++)
{
c[i]=min(T[i].mx,c[i]+(now-tim)*T[i].dl);
if(D[y].hel>=c[i])
{
D[y].hel-=c[i],
c[i]=0;
}
else
{
c[i]-=D[y].hel,
D[y].hel=0,
sta=1;
}
}
}
inline void Work2()
{
for(int x=1;x<=n;x+=sz)
{
tim=-1e9;
sta=1;
memset(sum,0,sizeof(sum));
for(int i=B[x].l;i<=B[x].r;i++)
{
cnt[i]=T[i].mx/T[i].dl,
lst[i]=T[i].mx%T[i].dl,
sum[1]+=T[i].dl,
sum[cnt[i]+1]-=T[i].dl-lst[i],
sum[cnt[i]+2]-=lst[i];
}
for(int i=1;i<=4e5;i++)
sum[i]+=sum[i-1];
for(int i=1;i<=4e5;i++)
sum[i]+=sum[i-1];
/*
需要解释的是sum的处理,每一个塔随着时间能量先上升后不变,所以做第一次差分,又因为要统计所有塔的能量和,所以总共做两次前缀和得出答案。
*/
for(int i=1;i<=m;i++)
{
if(D[i].hel==0)
continue;
int now=D[i].tim+B[x].l-1;
if(sta)
{
Work1(x,i);
}
else
{
if(D[i].hel<sum[now-tim])
{
// for(int j=B[x].l;j<=B[x].r;j++)
// c[i]=0;
Work1(x,i);
}
else
{
D[i].hel-=sum[now-tim];
D[i].hel=max(D[i].hel,0ll);
}
}
tim=now;
}
}
}
signed main()
{
// freopen("dinosaurs.in","r",stdin);
// freopen("dinosaurs.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&T[i].mx,&T[i].dl);
scanf("%lld",&m);
for(int i=1;i<=m;i++)
scanf("%lld%lld",&D[i].tim,&D[i].hel);
Init();
Work2();
for(int i=1;i<=m;i++)
ans+=D[i].hel;
printf("%lld\n",ans);
return 0;
}
题目见洛谷。
正在学习拉格朗日插值法。