\(\sf CF1427D\ Unshuffling\ a\ Deck\)
一些废话
考完之后发现这题竟然做过… 我学了个寂寞。
解法
\(\text{Method 1}\)
一种思路是维护 \([1,i]\) 区间的连贯性,更具体地说,是 \(1\rightarrow i\) 的前缀与 \(i\rightarrow 1\) 的后缀。首先将 \(1\) 挪到 \(1/n\) 号位。
假设目前维护了 \(1\rightarrow i\) 的前缀,我们将 \(i+1\) 接上去,令其位置为 \(p\)。在 \([1,i]\) 之间每个位置都切一刀,再将剩余的区间划分为 \([i+1,p],[p+1,n]\)。另一种情况同理。
因为只用维护到 \(n-1\) 位,所以到现在至多使用 \(n-1\) 次。最后可能整个是反的,在所有位置切一刀即可。
\(\text{Method 2}\)
如果区间无序,那么一定存在 \(i,j\) 使得 \(a_i=a_j+1\)。
我们将区间划分成 \([1,i-1],[i,k],[k+1,j],[j+1,n]\),将它们拼在一起。但是还需要保证不将已经有序的数对分割,由于 \(a_i=a_j+1\),所以肯定存在 \(k\in[i,j-1]\) 满足 \((k,k+1)\) 未被配对。
另外,这也保证 \((i-1,i),(j,j+1)\) 未被配对。
\(\sf CF1473E\ Minimum\ Path\)
解法
这个表达式实际上是一条路径,去掉最大值,最小值加两次。更进一步,其实并不用考虑 "最大、最小" 的限制,只需要令 "一条边不选,一条边选两次" 就一定符合条件。
令 \(d_{i,0/1,0/1}\) 表示是否选择 "一条边不选","一条边选两次" 的最短路。实现时可以拆成四个点。一种是直接连边,另一种是跑最短路时转移。
需要注意的是要连一条 \(i\rightarrow j+3n\) 的 \(w(i,j)\) 的边(两个条件都不满足到两个条件都满足),这是为了特判路径长度为 \(1\) 的情况。可以发现这不会影响正确答案,因为如果有最大/小值可以选,一定会更优。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f |= (s=='-');
while(s>='0' and s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const ll inf = 1e17;
int n,m,cnt,head[maxn];
bool vis[maxn<<2];
ll d[maxn<<2];
struct edge {
int nxt,to,w;
} e[maxn<<1];
struct node {
int v; ll w;
node() {}
node(int V,ll W):v(V),w(W) {}
bool operator < (const node &t) const {
return w>t.w;
}
};
priority_queue <node> q;
void addEdge(int u,int v,int w) {
e[++cnt].w=w;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
e[++cnt].w=w;
e[cnt].to=u;
e[cnt].nxt=head[v];
head[v]=cnt;
}
int findOri(int x) {
return x%n==0?n:x%n;
}
void update(int x,int y,int w) {
if(d[y]>d[x]+w)
d[y]=d[x]+w,q.push(node(y,d[y]));
}
void Dijkstra() {
for(int i=2;i<=n*4;++i)
d[i]=inf;
q.push(node(1,0));
while(!q.empty()) {
node t=q.top(); q.pop();
if(vis[t.v] or (d[t.v]^t.w))
continue;
vis[t.v]=1;
int u=findOri(t.v);
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(t.v<=n) {
update(t.v,v,e[i].w);
update(t.v,v+n,0);
update(t.v,v+n*2,e[i].w<<1);
update(t.v,v+n*3,e[i].w);
}
else if(t.v<=n*2) {
update(t.v,v+n,e[i].w);
update(t.v,v+n*3,e[i].w<<1);
}
else if(t.v<=n*3) {
update(t.v,v+n*2,e[i].w);
update(t.v,v+n*3,0);
}
else {
update(t.v,v+n*3,e[i].w);
}
}
}
}
int main() {
n=read(9),m=read(9);
for(int i=1;i<=m;++i) {
int u,v,w;
u=read(9),v=read(9),w=read(9);
addEdge(u,v,w);
}
Dijkstra();
for(int i=2;i<=n;++i)
print(d[i+n*3],' ');
puts("");
return 0;
}
\(\sf [CCO\ 2019]\ Sirtet\)
解法
事实上,如果求出每个 \(\rm sand\) 停止运动的时间,就可以通过移动行的方式得到答案。
首先将相连的 \(\rm sand\) 用并查集缩起来。对于两个在一列且相邻的 \(\rm sand\),不妨令其为 \((i,p),(j,p)(i<j)\),必定满足 \(t_{(i,p)}\le t_{(j,p)}+(j-i)-1\)。注意到我们需要求满足条件的最大的 \(t\)。所以这就是一个差分约束系统的模型,时间复杂度 \(\mathcal O(nm\log nm)\)。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f |= (s=='-');
while(s>='0' and s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <queue>
#include <cstring>
using namespace std;
const int maxn = 1e6+6;
int n,m,f[maxn],lst[maxn];
int cnt,head[maxn],d[maxn];
char **g,**ans;
bool vis[maxn];
struct edge {
int nxt,to,w;
} e[maxn*3];
struct node {
int v,w;
node() {}
node(int V,int W):v(V),w(W) {}
bool operator < (const node &t) const {
return w>t.w;
}
};
priority_queue <node> q;
#define getMemo(a) do { \
a = new char *[n+1]; \
for(int i=1;i<=n;++i) \
a[i] = new char [m+1]; \
} while(0);
int Find(int x) {
return x==f[x]?x:f[x]=Find(f[x]);
}
int ID(int x,int y) {
return (x-1)*m+y;
}
void merge(int x,int y) {
x=Find(x),y=Find(y);
if(x^y) f[x]=y;
}
void addEdge(int u,int v,int w) {
u=Find(u),v=Find(v);
e[++cnt].w=w;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void Dijkstra() {
memset(d,0x3f,sizeof d);
q.push(node(n*m+1,d[n*m+1]=0));
while(!q.empty()) {
node t=q.top(); q.pop();
if(vis[t.v] or (t.w^d[t.v]))
continue;
vis[t.v]=1;
for(int i=head[t.v];i;i=e[i].nxt) {
int v=e[i].to;
if(d[v]>d[t.v]+e[i].w)
d[v]=d[t.v]+e[i].w,
q.push(node(v,d[v]));
}
}
}
int main() {
n=read(9),m=read(9);
getMemo(g); getMemo(ans);
for(int i=1;i<=n;++i)
scanf("%s",g[i]+1);
for(int i=1;i<=n*m+1;++i)
f[i]=i;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) {
if(g[i][j]^'#') continue;
if(i>1 and g[i-1][j]=='#')
merge(ID(i,j),ID(i-1,j));
if(j>1 and g[i][j-1]=='#')
merge(ID(i,j),ID(i,j-1));
if(i<n and g[i+1][j]=='#')
merge(ID(i,j),ID(i+1,j));
if(j<m and g[i][j+1]=='#')
merge(ID(i,j),ID(i,j+1));
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) {
if(g[i][j]^'#') continue;
if(lst[j])
addEdge(ID(i,j),ID(lst[j],j),i-lst[j]-1);
lst[j]=i;
}
for(int i=1;i<=m;++i)
if(lst[i])
addEdge(n*m+1,ID(lst[i],i),n-lst[i]);
Dijkstra();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ans[i][j]='.';
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(g[i][j]=='#')
ans[i+d[Find(ID(i,j))]][j]='#';
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j)
putchar(ans[i][j]);
puts("");
}
delete [] g;
delete [] ans;
return 0;
}
「ROI 2019 Day 2」课桌
解法
这道题不会严格证明,只给出主观臆测的 "结论":
- 将同组的兔子按身高升序排序,相邻两只兔子坐一张桌子。
- 对于有用的桌子 \(i,j\),它们管辖的区间一定不是包含关系。这个可以证明,因为包含另一个区间的区间一定不劣。
- 因此可以将桌子排序。假设某张桌子排序后的编号为 \(i\),那么它对应第 \(i\) 组兔子。而且对应关系是单调的,也即对于两组 "一对兔子" \(i<j\),它们选择的 最优 桌子 \(p_i,p_j\),一定满足 \(p_i\le p_j\)。
由此我们可以抛开 "组" 这个单位讨论问题,把 \(m\) 组中相同次序的兔子对提出来,因为它们共用一张桌子,所以单独求出这 \(2m\) 只兔子最优桌子即可。
实现时,由于 结论叁
,可以写决策单调性分治。如果不预处理相同次序的兔子,而且查找在 \([L_i,R_i]\) 内的兔子用 lower_bound()
的话就有两只 \(\log\)。严格 \(\mathcal O(k\log n)\) 除了预处理相同次序的兔子,还要对桌子排序,这样查找的时候就可以双指针了。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f |= (s=='-');
while(s>='0' and s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <numeric>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int m,n,k,**h,tot;
ll s[maxn<<1],sm[maxn<<1],ans;
struct node {
int l,r;
} a[maxn],b[maxn];
void dicon(int l,int r,int L,int R) {
if(l>r) return;
int mid=l+r>>1,pos; ll mn = 1e17;
for(int i=1;i<=m;++i)
s[(i<<1)-1]=h[i][(mid<<1)-1],
s[i<<1]=h[i][mid<<1];
sort(s+1,s+(m<<1)+1);
partial_sum(s+1,s+(m<<1)+1,sm+1);
for(int i=L;i<=R;++i) {
int rr = upper_bound(s+1,s+(m<<1)+1,b[i].r)-s-1;
int lll = lower_bound(s+1,s+(m<<1)+1,b[i].l)-s;
ll tmp = 1ll*b[i].l*(lll-1)-sm[lll-1]+(sm[m<<1]-sm[rr])-1ll*b[i].r*(m*2-rr);
if(tmp<mn) mn=tmp,pos=i;
}
ans += mn;
dicon(l,mid-1,L,pos); dicon(mid+1,r,pos,R);
}
#define getMemo(a) do { \
a = new int *[m+1]; \
for(int i=1;i<=m;++i) \
a[i] = new int [(n<<1)+1]; \
} while(0);
int main() {
m=read(9),n=read(9),k=read(9);
for(int i=1;i<=k;++i)
a[i].l=read(9),a[i].r=read(9);
sort(a+1,a+k+1,[](const node &x,const node &y) {
return x.l<y.l or (x.l==y.l and x.r<y.r);
});
int R=0;
for(int i=1;i<=k;++i)
if(R<a[i].r)
R=a[i].r,b[++tot]=a[i];
getMemo(h);
for(int i=1;i<=m;++i) {
for(int j=1;j<=(n<<1);++j)
h[i][j]=read(9);
sort(h[i]+1,h[i]+(n<<1)+1);
}
dicon(1,n,1,tot);
print(ans,'\n');
delete [] h;
return 0;
}