UNR #4
序列妙妙值
题目描述
解法
我真的感觉这题并不简单,但是好像大家都会的样子。
设 \(dp[i][j]\) 表示前 \(i\) 个数划分成 \(j\) 段的最小代价,设 \(s_i=\sum_{j=1}^i a_j\),转移:
这个转移虽然形式简洁,但是没有什么性质也难以直接套数据结构。不妨先考虑 \(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));
}