UNR #4

序列妙妙值

题目描述

点此看题

解法

我真的感觉这题并不简单,但是好像大家都会的样子

\(dp[i][j]\) 表示前 \(i\) 个数划分成 \(j\) 段的最小代价,设 \(s_i=\sum_{j=1}^i a_j\),转移:

\[dp[i][j]\leftarrow dp[k][j-1]+(s_i\oplus s_{k}) \]

这个转移虽然形式简洁,但是没有什么性质也难以直接套数据结构。不妨先考虑 \(a_i<2^8\) 的部分分,可以维护 \(2^8\) 个桶,每个桶表示这个 \(s\) 值对应最小的 \(dp[k][j-1]\),时间复杂度 \(O(nkv)\)

考虑把上面的做法推广到 \(a_i<2^{16}\),自然想到值域分块,目的是平衡修改的查询的时间(原来是 \(O(1)-O(v)\) 的,尝试优化成 \(O(\sqrt v)-O(\sqrt v)\)),维护 \(f[u][v]\) 表示假设 \(s_i\) 的前 \(8\) 位为 \(u\),已知 \(s_j\) 的后 \(8\) 位为 \(v\),最小的代价是多少(注意前 \(8\) 位代表较大的数位,前 \(8\) 位的代价在修改时放进 \(f\) 中,后 \(8\) 位的代价在查询时统计)

时间复杂度 \(O(nk\sqrt v)\)写完博客突然感觉这题在哪里见过

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 60005;
const int inf = 0x3f3f3f3f;
int read()
{
    int x=0,f=1;char c;
    while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return x*f;
}
int n,k,a[M],dp[M][10],f[1<<8][1<<8];
void upd(int &x,int y) {x=min(x,y);}
int main()
{
    n=read();k=read();
    for(int i=1;i<=n;i++)
        a[i]=read()^a[i-1];
    memset(dp,0x3f,sizeof dp);
    dp[0][0]=0;
    for(int j=1;j<=k;j++)
    {
        for(int i=0;i<(1<<16);i++)
            f[i>>8][i&255]=inf;
        for(int i=j;i<=n;i++)
        {
            for(int k=0;k<(1<<8);k++)
            {
                int x=(k^(a[i-1]>>8))<<8;
                upd(f[k][a[i-1]&255],dp[i-1][j-1]+x);
            }
            for(int k=0;k<(1<<8);k++)
                upd(dp[i][j],f[a[i]>>8][k]+((a[i]^k)&255));
        }
    }
    for(int i=k;i<=n;i++)
        printf("%d ",dp[i][k]);
}

网络恢复

题目描述

点此看题

解法

交互题一定要注意特殊性质的部分分,很多时候有一个特殊性质的做法可以直接推广到正解。

考虑树的部分分,从叶子往上确定整棵树。那么我们给每个点随机一个 \(2^{64}\) 以内的权值,如果点 \(u\)\(b_u\) 是某个 \(v\)\(a_v\),那么可以认为 \(u\) 是叶子且 \((u,v)\) 直接有边相连,把 \(u\) 删除之后循环这个过程,只消耗一次询问。

上面的做法其实只要无环就可以求出原图的所有边,那么考虑把原图的边划分为若干个无环的子图,对这些子图分别用树的方法就可以得到答案。具体来说我们循环一下过程 \(10\) 次:随机一个边的排列,把这个排列 \(5\) 等分,就获得了 \(5\) 个子图,注意一条边可能被确定多次,需要判断一下,实测可以获得 \(70\) 分。

边数很多时构成环的概率很大,但是由于数据随机的原因,很难有导出子图满足每个点度数都 \(\geq 3\),所以我们只需要支持在不存在叶子时,确定某个度数 \(=2\) 的点的边即可。可以把所有点都拿出来,然后随机两个点,假设它们之间有边然后断掉,如果此时出现了真的叶子,那么可以判定它们真的有边,就达到了判断环的目的。

所以我们把原图的边直接 \(50\) 等分,然后魔改一下树的做法使其可以支持一些简单环的拆解,可以获得 \(100\) 分。

#include "explore.h"
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define vc vector
#define ull unsigned long long
const int M = 50005;
//
int n,m,k,h[M],q[M];vc<ull> a,b;
__gnu_pbds::gp_hash_table<ull,int> id;
__gnu_pbds::gp_hash_table<int,int> e[M];
mt19937_64 X(time(0));mt19937 Y(time(0));
void report(int x,int y)
{
    if(x>y) swap(x,y);
    if(!e[x][y]) Report(x+1,y+1);
    e[x][y]=1;
    b[x]^=a[y];b[y]^=a[x];
    if(id[b[x]]) q[++k]=x;
    if(id[b[y]]) q[++k]=y;
}
void work(int l,int r)
{
    vc<int> t;
    for(int i=l;i<=r;i++)
        t.push_back(i);
    b=Query(a,t);
    for(int i=0;i<n;i++)
        if(id[b[i]]) q[++k]=i;
    while(1)
    {
        if(k)
        {
            int u=q[k--];
            if(b[u]) report(u,id[b[u]]-1);
        }
        else
        {
            int l=0;
            for(int i=0;i<n;i++)
                if(b[i]) h[l++]=i;
            if(!l) return ;
            while(1)
            {
                int x=rand()%(l-1)+1,y=rand()%x;
                x=h[x];y=h[y];
                if(id[b[x]^a[y]] || id[b[y]^a[x]])
                    {report(x,y);break;}
            }
        }
    }
}
void Solve(int n1,int m1)
{
    n=n1;m=m1;
    for(int i=0;i<n;i++)
        a.push_back(X()),id[a[i]]=i+1;
    int bk=(m+49)/50;
    for(int i=1;i<=50;i++)
        work(i*bk-bk+1,min(i*bk,m));
}
posted @ 2022-08-09 19:03  C202044zxy  阅读(334)  评论(10编辑  收藏  举报