8VC Venture Cup 2016 - Final Round (CF627)

发布时间 2023-11-01 15:23:27作者: 樱雪喵

A. XOR Equation

最低位没有加法进位产生的影响,考虑从低位向高位 dp。
\(f_{i,0/1}\) 表示正在考虑第 \(i\) 位,前 \(i-1\) 位都满足限制,有无进位的方案数。
转移的时候枚举这一位两个数分别填 \(a,b\)\(x_i\) 表示 \(x\) 在二进制下的第 \(i\) 位,有

\[f_{i,[a+b+j>1]}=\sum f_{i-1,j}[a\oplus b=x_i,(a+b+j)\bmod 2=s_i] \]

特判一下 \(s=x\),因为这样 \(a,b\) 会有 \(0\)

Code
#define int long long
const int N=105;
int f[N][2],s,x;
il int get(int x,int i) {return x>>i&1;}
signed main()
{
    s=read(),x=read();
    int n=__lg(max(x,s))+1;
    f[0][0]=1;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=1;j++)
            for(int a=0;a<2;a++)
                for(int b=0;b<2;b++)
                {
                    if((a^b)!=get(x,i)) continue;
                    if((a+b+j)%2!=get(s,i)) continue;
                    f[i+1][(a+b+j)/2]+=f[i][j];
                }
    if(s==x) f[n][0]-=2;
    printf("%lld\n",f[n][0]);
    return 0;
}

B. Factory Repairs

线段树板子。

C. Package Delivery

快看,这里有 CSP J 2023 T2 原题!
直接按费用从小到大贪心就行。

D. Preorder Test

最小值最大,考虑二分答案。问题转化为判断能否使 dfs 序的前 \(k\) 位都大于等于 \(mid\)
树形 dp,设 \(f_i\) 表示以 \(i\) 为根的子树,dfs 序最多前 \(f_i\) 个位置满足条件。
那么对于一个点 \(u\),转移是这样的:

  • \(u<mid\)\(f_u\) 一定是 \(0\)
  • 否则,对于所有 \(f_v=siz_v\) 都可以加入 \(f_u\),在此基础上加 \(f_v<siz_v\) 最大的 \(f_v\)
    其实这样换根就可以了,但是没有必要。以 \(x\) 为整棵树的根的答案可以直接用已有的 dp 值得出。
Code
const int N=2e5+5;
int n,m,rt,minn=2e9,a[N];
int head[N],cnt;
struct node{
	int nxt,to;
}e[N<<1];
void add(int u,int v){
	e[++cnt]={head[u],v};head[u]=cnt;
}
int siz[N],f[N];
int ans=0;
void dfs(int now,int fa,int x)
{
	f[now]=siz[now]=1;
	int mx1=0,mx2=0;
	for(int i=head[now];i;i=e[i].nxt)
	{
		int v=e[i].to;if(v==fa) continue;
		dfs(v,now,x);
		siz[now]+=siz[v];
		if(f[v]==siz[v]) f[now]+=f[v];
		else if(f[v]>mx1) mx2=mx1,mx1=f[v];
		else if(f[v]>mx2) mx2=f[v];
	}
	if(a[now]<x) f[now]=0;
	else f[now]+=mx1;
	ans=max(ans,f[now]+mx2);
}
bool check(int x)
{
	memset(siz,0,sizeof(siz));
	memset(f,0,sizeof(f));ans=0; 
	dfs(rt,0,x); 
	return ans>=m;
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) if(a[i]<minn) minn=a[i],rt=i;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	int l=1,r=1e6;
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	printf("%d\n",l);
	return 0;
}

E. Orchestra

好厉害啊这个题。
首先把问题转化成总数减不合法的。发现 \(k\) 很小,考虑在这上面搞点东西。
将所有给定的点按纵坐标为第一关键字,横坐标为第二关键字排序,并重新标号。对点 \(i\) 维护 \(cnt_i\) 表示 \(i\) 往后数第 \(k\) 个点,这个点所在的纵坐标。
那么我们只要维护了 \(cnt_i\),就能求出答案。
首先枚举矩形的上边界,倒序枚举下边界,并从集合中删点。这可以使用双向链表维护,一次删点至多会对它前面的 \(k\) 个点的 \(cnt\) 值产生贡献,暴力更新他们即可。
时间复杂度 \(O(n^2k)\),但是它真的好难调。

Code
#define int long long
const int N=3005;
int n,m,t,k,tot;
struct node{int x,y;} a[N];
int b[N];
il bool cmp(node x,node y) {return x.y==y.y?x.x<y.x:x.y<y.y;}
int flag[N],vis[N],lst[N],nxt[N];
int Ans,ans,cnt[N],Cnt[N],len[N],L[N],R[N];
vector<int> T[N];
void upd(int x)
{
    if(!x) return;
    if(cnt[x]) ans-=Cnt[x]; int now=x;
    for(int i=2;i<=k;i++) now=nxt[now];
    cnt[x]=a[now].y;
    Cnt[x]=(a[x].y-L[a[x].y])*(m-cnt[x]+1);
    ans+=Cnt[x];
}
int solve(int st)
{
    tot=0,ans=0; int Ans=0; a[t+1].y=m+1;
    for(int i=1;i<=n;i++) T[i].clear();
    for(int i=1;i<=m;i++) vis[i]=0,L[i]=0,R[i]=0;
    for(int i=1;i<=t;i++) flag[i]=0,cnt[i]=0;

    for(int i=1;i<=t;i++) 
    {
        if(a[i].x>=st) 
        {
            b[++tot]=i;
            T[a[i].x].push_back(i);
            if(!vis[a[i].y]) vis[a[i].y]=1,flag[i]=1;
        }
    }
    int lt=0;
    for(int i=1;i<=m;i++) if(vis[i]) L[i]=lt,lt=i;
    lt=m+1;
    for(int i=m;i;i--) if(vis[i]) R[i]=lt,lt=i;
    b[tot+1]=t+1,nxt[t+1]=t+1;
    b[0]=0,lst[0]=0;
    for(int i=1;i<=tot;i++) lst[b[i]]=b[i-1],nxt[b[i]]=b[i+1];
    for(int i=1;i<=tot;i++) if(flag[b[i]]) upd(b[i]);
    for(int i=n;i>=st;i--)
    {
        Ans+=ans;
        for(auto x:T[i])
        {
            if(flag[x]) 
            {
                ans-=Cnt[x],cnt[x]=m+1;
                R[L[a[x].y]]=R[a[x].y],L[R[a[x].y]]=L[a[x].y];
                upd(nxt[x]);
            } 
            nxt[lst[x]]=nxt[x],lst[nxt[x]]=lst[x];
            int now=x;
            for(int i=2;i<=k;i++) 
            {
                now=lst[now];
                if(flag[now]) upd(now);
            }
        }
    }
    return Ans;
}
signed main()
{
    n=read(),m=read(),t=read(),k=read();
    for(int i=1;i<=t;i++) a[i].x=read(),a[i].y=read();
    sort(a+1,a+t+1,cmp);
    for(int i=1;i<=n;i++) Ans+=solve(i);
    printf("%lld\n",Ans);
    return 0;
}

F. Island Puzzle

考虑先将 \(0\) 移到目标位置。

手玩一下可以发现,如果不加一条边,我们把 \(0\) 移到它的目标位置上的方案是唯一确定的。因为走重复路径的话交换操作会相互抵消。
那么满足以上条件的最终局面也是唯一确定的,判断它与目标是否完全一致即可。

若不一致,则表明需要加边。加边 \((x,y)\) 后,这条边与原树上的路径形成了一个环。把 \(0\) 从根移到环上,则 \(0\) 每在这个环上绕一圈,这个环上的点循环移动一位。
对于可行性,判断初始局面与目标局面不一样的位置是否形成循环位移。
最少次数要考虑正着转和反着转的问题,同时去掉重复的路径。这一步的细节很多,建议配合代码食用。
代码中的 LCA 是不必要的,因为查询次数只有 \(O(1)\),完全可以跑暴力。这里是因为懒得写直接粘了个板子。因为一些神秘细节调了大概有 4h。

Code
const int N=2e5+5;
int n,rt,pos[N],a[N],b[N];
int dfn[N],tot,dep[N],st[20][N],Fa[N];
vector<int> e[N];
struct LCA
{
    il int get(int x,int y) {return dep[x]<dep[y]?x:y;}
    void dfs(int u,int fa)
    {
        Fa[u]=fa;
        dfn[u]=++tot,st[0][tot]=fa; dep[u]=dep[fa]+1;
        for(auto v:e[u]) if(v!=fa) dfs(v,u);
    }
    il void init()
    {
        dfs(rt,0);
        for(int i=1;(1<<i)<=n;i++)
            for(int j=1;j<=n-(1<<i)+1;j++)
                st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
    }
    il int lca(int x,int y)
    {
        if(x==y) return x;
        if((x=dfn[x])>(y=dfn[y])) swap(x,y);
        int l=__lg(y-x);
        return get(st[l][x+1],st[l][y-(1<<l)+1]);
    }
}l;
int vis[N],ct[N],ps[N],fg[N];
vector<int> s,t;
il void fail() {printf("-1\n");exit(0);}
void dfs(int u,int fa)
{
    if(a[u]!=b[u]) s.push_back(a[u]),t.push_back(b[u]);
    for(auto v:e[u]) if(vis[v]&&v!=fa) 
    {
        if(dep[v]<dep[u]) fg[u]=1;
        else fg[v]=-1;
        dfs(v,u);
    }
}
il int getdis(int x,int y)
{
    return dep[x]+dep[y]-2*dep[l.lca(x,y)];
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read(),pos[a[i]]=i;
    for(int i=1;i<=n;i++) 
    {
        b[i]=read();
        if(!b[i]) rt=i;
    }
    for(int i=1;i<n;i++) 
    {
        int u=read(),v=read();
        e[u].push_back(v),e[v].push_back(u);
    }
    l.init();
    int lst=pos[0],ans=0;
    while(pos[0]!=rt)
    {
        ans++;
        int x=pos[0],y=Fa[x];
        swap(pos[0],pos[a[y]]);
        swap(a[x],a[y]);
        
    }
    int p=-1;
    for(int i=1;i<=n;i++) if(b[i]!=a[i]) 
    {
        if(p==-1||dep[Fa[i]]<dep[p]) p=Fa[i];
    }
    if(p==-1) {printf("0 %d\n",ans);return 0;}
    ans=0;
    for(int i=1;i<=n;i++) if(b[i]!=a[i]) vis[i]=1;
    vis[p]=1;
    for(int i=1;i<=n;i++) if(b[i]!=a[i])
    {
        if(!vis[Fa[i]]) fail();
        ct[Fa[i]]++;
    }
    int u=0,v=0;
    for(int i=1;i<=n;i++) 
    {
        if(ct[i]>2||(ct[i]==2&&i!=p)) fail();
        if((!ct[i]||ct[i]==1&&i==p)&&vis[i]) {if(!u) u=i;else if(!v) v=i;else fail();}
    }
    dfs(u,0); int len=s.size(),lt=-1e9;
    for(int i=0;i<len;i++) ps[s[i]]=i;
    for(int i=0;i<len;i++) 
        if(i==0) lt=ps[t[i]]-i>=0?ps[t[i]]-i:ps[t[i]]-i+len;
        else if((ps[t[i]]-i>=0?ps[t[i]]-i:ps[t[i]]-i+len)!=lt) fail();
    int dis=0,now=lst; lt=len-lt;
    while(now)
    {
        if(fg[now]==1) dis++;
        else if(fg[now]==-1) dis--;
        now=Fa[now];
    }
    if(!dis) 
    {
        ans=min(lt,len-lt)*(len+1)+dep[lst]-1+2*(dep[p]-1)-2*(dep[l.lca(lst,p)]-1);
    }
    else if(dis>0) ans=dep[lst]-1+min(lt*(len+1)-2*dis,(len-lt)*(len+1));
    else 
    {
        dis=-dis,ans=dep[lst]-1+min(lt*(len+1),(len-lt)*(len+1)-2*dis);
    }
    printf("%d %d %d\n",u,v,ans);
}