AB
沒什麼好說的。
C
把我卡了。dp非常明顯,最開始我想的是單向做,\(f[i][0/1]\)表示前\(i\)塊蛋糕已經分出去了,01表示Alice是否拿過了,此時分給了幾個人。
嘗試寫寫轉移就知道為什麼寄了。狀態不夠,沒法表示答案。就算轉移到了最後也沒法得出我們需要的答案。可以發現,這個dp不好做的原因就是alice取蛋糕的部分並不確定。
賽時的時候沒有轉換思路,非常篤定這是dp。但是其實做到這裡dp就已經被叉了。這個dp方程的形式已經沒有最佳化的機會了。如果要寫出正確的暴力dp方程,需要3維,其中光空間就\(n*m\),而這個狀態和轉移不滿足任何最佳化的形式。就算滿足了,寫個2C用斜率最佳化之類的也挺離譜的。。
事實上這是一個貪心。其實很容易發現,我們的操作裡面並不存在“捨棄一塊蛋糕”這種操作。假如不考慮alice,那這個題目就是掃一遍就能出答案。考慮一下答案的結構,其實一定是左邊一段給客人,右邊一段給客人,中間alice拿一段。給客人的直接貪心是有固定答案的,所以其實直接列舉就可以了。思考一下可以發現單調性非常明顯,固定一個端點二分或者倍增另一個端點就結束了。
其實會被卡,很大一部分是因為開始看錯題了。意外是alice必須取最後一段。那就是一個挺裸的dp?好吧,貪心。我居然寫了二分??無敵了。
有點抽象的。這種錯誤。
D
這題是真噁心。
很明顯,只能順序取,那就直接順序列舉每個點,然後看每個人是不是有價值更高的卡牌在之前能夠得到。這個玩意,樹狀陣列就能夠做到,單點修改然後區間查詢嘛。但是要求方案。值域樹狀陣列不是那麼好搞。迫不得已還寫了線段樹。。
一眼秒了,還讓我寫上百行。無敵了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
int n,a[200001],b[200001],c[2000001];
int f[200001][2];
inline int ls(int x){
return (x<<1);
}
inline int rs(int x){
return (x<<1)|1;
}
struct Seg_Tree
{
int val[8000001],From[8000001];
inline void push_up(int p)
{
if(val[ls(p)]==0&&val[rs(p)]==0)
{
val[p]=0;
From[p]=0;
return ;
}
if(val[ls(p)]>=val[rs(p)])
{
val[p]=val[ls(p)];
From[p]=From[ls(p)];
}
else
{
val[p]=val[rs(p)];
From[p]=From[rs(p)];
}
}
void build(int p,int l,int r)
{
if(l==r)
{
val[p]=0;From[p]=0;
return ;
}
int mid=l+r>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
}
void change(int p,int l,int r,int tar,int x,int Fa)
{
if(l==r&&l==tar)
{
val[p]=x;
From[p]=Fa;
return;
}
int mid=l+r>>1;
if(tar<=mid)change(ls(p),l,mid,tar,x,Fa);
if(tar>mid)change(rs(p),mid+1,r,tar,x,Fa);
push_up(p);
}
int query(int p,int l,int r,int tl,int tr)
{
if(tl<=l&&r<=tr)
{
return From[p];
}
int mid=l+r>>1;
int ans=0;
if(tl<=mid)ans=max(ans,query(ls(p),l,mid,tl,tr));
if(tr>mid)ans=max(ans,query(rs(p),mid+1,r,tl,tr));
return ans;
}
}A,B,C;
int main()
{
int T=read();
while(T--)
{
n=read();
for(int i=1;i<=n;i++)f[i][0]=f[i][1]=0;
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i++)
b[i]=read();
for(int i=1;i<=n;i++)
c[i]=read();
f[1][0]=1;
A.build(1,1,n);
B.build(1,1,n);
C.build(1,1,n);
A.change(1,1,n,a[1],1,1);
B.change(1,1,n,b[1],1,1);
C.change(1,1,n,c[1],1,1);
for(int i=2;i<=n;i++)
{
int a1=A.query(1,1,n,a[i],n);
int b1=B.query(1,1,n,b[i],n);
int c1=C.query(1,1,n,c[i],n);
if(a1!=0)f[i][0]=1,f[i][1]=a1;
else
if(b1!=0)f[i][0]=1,f[i][1]=b1;
else
if(c1!=0)f[i][0]=1,f[i][1]=c1;
if(f[i][0])
A.change(1,1,n,a[i],1,i),
B.change(1,1,n,b[i],1,i),
C.change(1,1,n,c[i],1,i);
}
// cout<<'4'<<endl;
if(!f[n][0])cout<<"No"<<'\n';
else
{
cout<<"Yes\n";
vector<pair<char,int>> ans;
int now=n;
while(now!=1)
{
int back=f[now][1];
if(a[back]>a[now])
ans.push_back({'q',now});
else
if(b[back]>b[now])
ans.push_back({'k',now});
else
ans.push_back({'j',now});
now=back;
}
cout<<ans.size()<<'\n';
for(int i=ans.size()-1;i>=0;i--)
{
cout<<ans[i].first<<' '<<ans[i].second<<'\n';
}
}
}
}
E
有點意思的題目。
其實先簡單寫寫表示式,就能夠發現是一個有後效性的轉移方程。那其實說這是一個線性方程組更適合。
但是這個線性方程組的相關引數非常固定,全是\(\frac 1 2\),那就很可能不需要用高斯消元了。
需要先考慮一下兩個人的行動策略。非常明顯,alice一定是向根節點走的,而阻止她的人一定是讓她往最近的葉子節點走的。
所以可以進行一個類似樹剖的過程,把樹剖成一個個鏈,剖的法則就是距離子樹裡面葉子最近的點剖在一條鏈上。
那麼,如果一次行動的起始點在這個鏈上,那麼,這次行動的結果很固定,要麼走到鏈的頭部,來到另一條鏈或者是終點,要麼是被推到葉子節點。
所以我們可以把問題簡化為在一根根不同長度的鏈上行動,然後把結果乘起來。
前面分析已經能得到了,這個方程是一個係數固定的線性方程組。會有變化的只有這個線性方程組的項數。可以考慮項數會帶來哪些變化。
手玩了一下不同項的方程,其實就能得到通解了。(雖然如果做的熟練的話,這個玩意看到的第一眼就知道是有一個和項數相關的通解的表示的)
對於一個在長度為\(d\)的鏈上的從上往下數第\(i\)個位置,從這裡走到鏈首的機率是\(1-\frac {i-1}d\)
如果這題改改,p不是\(1/2\),其實也能做,也是推到一下能得到通解,需要高斯消元解方程的是對於每個點\(p\)都不一樣。
然後這題就沒有了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read()
{
char c=getchar();ll a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
const ll Mod=998244353;
ll n;
struct edge
{
ll next,to;
}e[400001];
ll head[400001],tot,leaf[200001],down[200001][2],dep[200001];
inline void add(ll i,ll j)
{
e[++tot].next=head[i];
e[tot].to=j;
head[i]=tot;
}
ll ksm(ll x,ll a)
{
if(a==0)return 1;
if(a==1)return x;
if(a==2)return x*x;
if(a%2==0)
{
ll ans=ksm(x,a/2)%Mod;
ans=ans*ans%Mod;
return ans;
}
else
{
ll ans=ksm(x,a/2)%Mod;
ans=ans*ans%Mod*x%Mod;
return ans;
}
}
ll ans[2000001];
pair<ll,ll> dfs(ll x,ll fa)
{
dep[x]=dep[fa]+1;
ll cnt=0;
down[x][0]=0x3f3f3f3f;
for(ll i=head[x];i!=0;i=e[i].next)
{
ll u=e[i].to;
if(u==fa)continue;
cnt++;pair<ll,ll> Ned=dfs(u,x);
if(down[x][0]>Ned.first+1)
down[x][0]=Ned.first+1,down[x][1]=Ned.second;
}
if(cnt==0)leaf[x]=1,down[x][0]=0,down[x][1]=x;
else leaf[x]=0;
return {down[x][0],down[x][1]};
}
void dfs_ans(ll x,ll fa,ll now,ll up)
{
// if(leaf[x])ans[x]=0;
ll len=dep[down[x][1]]-dep[up];
ll now_place=dep[x]-dep[up]+1;
ans[x]=ans[now]*(len-now_place+1)%Mod*ksm(len,Mod-2)%Mod;
for(ll i=head[x];i!=0;i=e[i].next)
{
ll u=e[i].to;
if(u==fa)continue;
if(down[u][1]==down[x][1])
dfs_ans(u,x,now,up);
else
dfs_ans(u,x,x,x);
}
}
int main()
{
ll T=read();
while(T--)
{
n=read();
for(ll i=0;i<=tot;i++)head[i]=0;tot=0;
for(ll i=1;i<n;i++)
{
ll x=read(),y=read();
add(x,y);add(y,x);
}
dfs(1,0);
ans[1]=1;
dfs_ans(1,0,1,1);
// cout<<1<<endl;
for(ll i=1;i<=n;i++)
{
cout<<ans[i]%Mod<<' ';
}
cout<<'\n';
}
return 0;
}