zoukankan      html  css  js  c++  java
  • [Contest on 2021.10.15] 细思极恐

    (sf CF1427D Unshuffling a Deck)

    一些废话

    考完之后发现这题竟然做过… 我学了个寂寞。

    解法

    ( ext{Method 1})

    一种思路是维护 ([1,i]) 区间的连贯性,更具体地说,是 (1 ightarrow i) 的前缀与 (i ightarrow 1) 的后缀。首先将 (1) 挪到 (1/n) 号位。

    假设目前维护了 (1 ightarrow i) 的前缀,我们将 (i+1) 接上去,令其位置为 (p)。在 ([1,i]) 之间每个位置都切一刀,再将剩余的区间划分为 ([i+1,p],[p+1,n])。另一种情况同理。

    因为只用维护到 (n-1) 位,所以到现在至多使用 (n-1) 次。最后可能整个是反的,在所有位置切一刀即可。

    ( ext{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),所以肯定存在 (kin[i,j-1]) 满足 ((k,k+1)) 未被配对。

    另外,这也保证 ((i-1,i),(j,j+1)) 未被配对。

    (sf CF1473E Minimum Path)

    解法

    这个表达式实际上是一条路径,去掉最大值,最小值加两次。更进一步,其实并不用考虑 "最大、最小" 的限制,只需要令 "一条边不选,一条边选两次" 就一定符合条件。

    (d_{i,0/1,0/1}) 表示是否选择 "一条边不选","一条边选两次" 的最短路。实现时可以拆成四个点。一种是直接连边,另一种是跑最短路时转移。

    需要注意的是要连一条 (i ightarrow 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)

    解法

    事实上,如果求出每个 ( m sand) 停止运动的时间,就可以通过移动行的方式得到答案。

    首先将相连的 ( m sand) 用并查集缩起来。对于两个在一列且相邻的 ( m sand),不妨令其为 ((i,p),(j,p)(i<j)),必定满足 (t_{(i,p)}le t_{(j,p)}+(j-i)-1)。注意到我们需要求满足条件的最大的 (t)。所以这就是一个差分约束系统的模型,时间复杂度 (mathcal O(nmlog 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_ile p_j)

    由此我们可以抛开 "组" 这个单位讨论问题,把 (m) 组中相同次序的兔子对提出来,因为它们共用一张桌子,所以单独求出这 (2m) 只兔子最优桌子即可。

    实现时,由于 结论叁,可以写决策单调性分治。如果不预处理相同次序的兔子,而且查找在 ([L_i,R_i]) 内的兔子用 lower_bound() 的话就有两只 (log)。严格 (mathcal O(klog 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,'
    ');
    	delete [] h;
    	return 0;
    }
    
  • 相关阅读:
    iOS 5.0 后UIViewController新增:willMoveToParentViewController和didMoveToParentViewCon[转]
    数字统计(0)<P2010_1>
    数字反转(0)<P2011_1>
    质因数分解(0)<P2012_1>
    记数问题(0)<P2013_1>
    珠心算测验(0)<P2014_1>
    金币(0)<P2015_1>
    归并排序
    循环语句(while语句和do...while语句)
    循环语句(for语句的用法)
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15413067.html
Copyright © 2011-2022 走看看