5月26日

5月26日

Love is the best medicine.

前传

“同学们,这种试卷我们明天订正完‘反交’一下。”——5.25 Mr 袁

这就是生物的魅力吗?

“赫鲁晓夫提出了两个条件:一个是要到美国的肯尼迪(???)玩?”——Miss 殷

“嗯???” 全班

“哦不对,迪士尼” 笑

第二次“没有答应他去肯尼迪”????

这就是历史的魅力吗

CF1527E Partition Game

线段树优化dp

对于暴力 \(dp[i][j]\) 表示前i个分为j段的值

转移 \(dp[i][k] = min \{dp[j][k-1]+cost(j+1,i)\}\)

对于cost,从后往前枚举 \(j\) 计算时只要加入 \(j+1\) 位置和后面第一个的差就可以 \(O(n^2k)\) 完成了。

如果从前忘后算,加入 \(i\) 的时候,也就是将 \([1,lst]\) 的代价都加 \(i-lst\) 就可以用线段树区间加求最小值啦!枚举一下k,就可以 \(O(kn log n)\) 啦!

#include <bits/stdc++.h>
typedef long long ll;
const int N=35005;
const ll INF=1e15;
ll dp[N];
int a[N],pre[N],n,k,lst[N];
struct SGT{
	ll t[N<<2],tg[N<<2];
	void build(int k,int L,int R){
		tg[k]=0;
		if (L==R){
			t[k]=dp[L-1];
			return;
		}
		int mid=(L+R)>>1;
		build(k<<1,L,mid),build(k<<1|1,mid+1,R);
		t[k]=std::min(t[k<<1],t[k<<1|1]);
	}
	void puttag(int k,int x){
		tg[k]+=x;
		t[k]+=x;
	}
	void pushdown(int k){
		if (tg[k]){
			puttag(k<<1,tg[k]);
			puttag(k<<1|1,tg[k]);
			tg[k]=0;
		}
	}
	void modify(int k,int L,int R,int l,int r,int x){
		if (r<l) return;
		if (L==l && R==r){
			puttag(k,x);
			return;
		}
		pushdown(k);
		int mid=(L+R)>>1;
		if (r<=mid) modify(k<<1,L,mid,l,r,x);
		else if (l>mid) modify(k<<1|1,mid+1,R,l,r,x);
		else{
			modify(k<<1,L,mid,l,mid,x);
			modify(k<<1|1,mid+1,R,mid+1,r,x);
		}
		t[k]=std::min(t[k<<1],t[k<<1|1]);
	}
	ll query(int k,int L,int R,int l,int r){
		if (l==L && R==r) return t[k];
		pushdown(k);
		int mid=(L+R)>>1;
		if (r<=mid) return query(k<<1,L,mid,l,r);
		if (l>mid) return query(k<<1|1,mid+1,R,l,r);
		return std::min(query(k<<1,L,mid,l,mid),query(k<<1|1,mid+1,R,mid+1,r));
	}
}T;
int main(){
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]); 
	for (int i=1;i<=n;i++){
		pre[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for (int i=1;i<=k;i++){
		T.build(1,1,n);
		dp[i-1]=INF;
		for (int j=i;j<=n;j++){
			T.modify(1,1,n,1,pre[j],j-pre[j]);
			dp[j]=T.query(1,1,n,1,j);
		}
	}
	printf("%lld\n",dp[n]);
}

CF1515F Phoenix and Earthquake

题目名指路 歌曲名:涅槃 (Phoenix) 敲代码超配

构造

瞄了一眼发现什么boruvka,然后想到最后得出的猜测是。

我也不会boruvka怎么做(后来发现人家说他假了,我眼瞎),感觉要是和 \(<(n-1) \times k\) 一定无解。否则每次把可行的并掉一定就可以了吧。

至于怎么并可行的,找任意一棵生成树都是可行的,因为如果总和满足条件,一定有一条边是 \(>2k\) 的,否则达不到 \((n-1) \times k\)

然后的构造:

每次找到一个叶节点 \(u\)

  • 如果 \(a_u<k\) 那么把 \(u\) 在最后和父亲合起来,继续考虑剩下部分(剩下部分的和一定满足),压入栈
  • 否则就可以当前就把 \(u\)\(fa\) 连起来,变成 \(a_u+a_{fa}-k\),继续构造,直接输出

最后把第一类边倒序输出即可

#include <bits/stdc++.h>
typedef long long ll;
const int N=300005;
std::queue<int> q;
int n,m,k,f[N],to[N<<1],edge,Next[N<<1],last[N],id[N<<1],fw[N],st[N],tp,x,y;
int d[N];
ll a[N],sum;
int find(int x){
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}
void add(int x,int y,int z){
	to[++edge]=y;
	Next[edge]=last[x];
	last[x]=edge;
	id[edge]=z;
}
void dfs(int x,int y,int z){
	f[x]=y,fw[x]=z;
	for (int i=last[x];i;i=Next[i]){
		int u=to[i];
		if (u==y) continue;
		dfs(u,x,id[i]);
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for (int i=1;i<=n;i++) f[i]=i;
	for (int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		sum+=a[i];
	} 
	if (sum<(ll)(n-1)*k) return puts("NO"),0; 
	puts("YES");
	for (int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		int fx=find(x),fy=find(y);
		if (fx==fy) continue;
		add(x,y,i),add(y,x,i);
		d[x]++,d[y]++;
		f[fx]=fy;
	}
	dfs(1,0,0);
	for (int i=2;i<=n;i++)
		if (d[i]==1) q.push(i);
	while (q.size()){
		int x=q.front(),fa=f[x];
		q.pop();
		if (!fa) continue;
		d[fa]--;
		if (d[fa]==1) q.push(fa);
		if (a[x]<k) st[++tp]=fw[x];
		else printf("%d\n",fw[x]),a[fa]=a[fa]+a[x]-k;
	}
	while (tp) printf("%d\n",st[tp--]);
}

CF1477D Nezzar and Hidden Permutations

拓扑序,构造

这题我以前一定看过,然而我想了半天还是不会做。

开始一下午一道题的摸鱼时光。

首先如果一个点度数是 \(n-1\) ,定向后这个点的拓扑序一定唯一确定了,可以把这个点去掉。

去完以后,最大点的度数 \(<n-1\),猜测此时已经有方案使两排列完全不相同。

考虑如果一张不止一个点的图中有一个孤立点,那么这个孤立点拓扑序可以任意,只要放在第一个+其他、其他+最后一个就可以了,称次为孤立图。

那么我们努力把剩下的图分成若干个有孤立点的集合,这样把这些图的拓扑序拼起来(一段一段拼就行)后也不会相同(只要按集合编号大小连定外面的边就行了)。这些孤立图在反图中的表现即为菊花图。建出反图的一棵生成树,从下往上处理 。如果子节点还未被加入某一个集合,那么和父亲并在一起,父亲为孤立点。到最后如果根成为了单独的点,任选一个它的儿子加入进去即可。

反图的边数数量很多,如何建一棵生成树?随便了。看到了一种简单的写法,虽然他是 \(O(n log n)\) 的,就这样吧。

#include <bits/stdc++.h>
using namespace std;
const int N=500005;
int T,n,m,x,y,d[N],match[N],a[N],b[N],siz[N],ap[N],bp[N];
vector<int> e[N],s[N];
queue<int> q;
void clear(){
	for (int i=1;i<=n;i++)
		d[i]=0,e[i].clear(),match[i]=0,s[i].clear();
}
int main(){
	scanf("%d",&T);
	while (T--){
		clear();
		scanf("%d%d",&n,&m);
		for (int i=1;i<=m;i++){
			scanf("%d%d",&x,&y);
			d[x]++,d[y]++;
			e[x].push_back(y),e[y].push_back(x); 
		} 
		for (int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
		int tp=0;
		for (int i=1;i<=n;i++){
			if (d[i]==n-1){
				a[++tp]=i;
				b[tp]=i;
				continue;
			}
			if (match[i]) continue;
			int p=0,u;
			for (u=1;u<=n;u++){
				if (u==i) continue;
				if (p>=e[i].size() || u!=e[i][p]){
					if (d[i]!=n-1) break;
				}else p++;
			}
			int rt=match[u];
			if (!rt){//新建 
				rt=u;
				match[i]=match[u]=u;
				siz[u]=2;
			}else{
				if (rt==u){//直接加入 
					siz[rt]++;
					match[i]=rt;
				}else{
					if (siz[rt]>2){//分裂 
						siz[rt]--;
						rt=u;
						match[u]=match[i]=u;
						siz[u]=2;
					}else{//换根 
						siz[u]=3;
						match[rt]=match[u]=match[i]=u;
					}
				}
			}
		}
		for (int i=1;i<=n;i++)
			if (match[i] && match[i]!=i) s[match[i]].push_back(i);
		for (int i=1;i<=n;i++){
			if (match[i]==i){
				a[tp+1]=i;
				b[tp+siz[i]]=i;
				for (int j=0;j<siz[i]-1;j++){
					a[tp+1+j+1]=s[i][j];
					b[tp+1+j]=s[i][j];
				}
				tp+=siz[i];
			}
		}
		for (int i=1;i<=tp;i++) ap[a[i]]=i,bp[b[i]]=i;
		for (int i=1;i<=n;i++) printf("%d ",ap[i]);puts("");
		for (int i=1;i<=n;i++) printf("%d ",bp[i]);puts(""); 
	}
}

CF1515G Phoenix and Odometers

昨天回家想了想。你保存了吗?保存了吗???

今天打开 哦 果然是一片空白,真是太棒了,清理一下心情。

我不想写了看这个就行了 好东西好东西

至于为什么弄了这么久,Longlong不要直接用abs,死的不明不白。

还学会了gcd的新写法,这样就不用管0了。

果断ctrl+s

哦它恢复回来了

想了一会儿.

对不起没有然后了我以为它是个无向图,还想着每条边都可以绕来绕去绕成余数为零。

重新开始!有向图,回路,那一定是一个强连通分量里的,不妨只考虑某一个。且两两间都有回路,那么易知,整个强连通分量对 \((s,t)\) 的答案是一样的,因为你可以从 \(v\) 绕一个回路绕t次就余数为0了,中途再从 \(u\) 绕出去一下,这样 \(u\) 能达到的 \(v\) 也行了。

同理也可以知道,所有的环都可以任意取到,如果把图中所有环长取出来,问的就是 \(k_1 len_1+k_2 len_2 + \dots k_t len_t +S\equiv 0 (\text{mod } T)\)

根据裴蜀定理可知,前面那一堆东西能表示出任意\(kg(g=gcd(len))\) 那么成立条件即为 \(gcd(g,T)|S\)

问题转化为怎么求所有环长

因为是有向图,所以任意一个环都可以表示成树上返祖边环的组合。

好东西

#include <bits/stdc++.h>
typedef long long ll;
const int N=200005;
int dfn[N],low[N],idn,col[N],to[N],last[N],Next[N],w[N],edge;
int x,y,n,Q,vis[N],st[N],tp,cnt,z,m,s,t;
ll d[N],a[N];
ll g;
ll gcd(ll x,ll y){
	return y?gcd(y,x%y):x;
}
void add(int x,int y,int z){
	to[++edge]=y;
	Next[edge]=last[x];
	last[x]=edge;
	w[edge]=z;
}
void tarjan(int x){
	dfn[x]=low[x]=++idn;
	vis[x]=1;
	st[++tp]=x;
	for (int i=last[x];i;i=Next[i]){
		int u=to[i];
		if (!dfn[u]){
			tarjan(u);
			low[x]=std::min(low[x],low[u]);
		}else if (vis[u]) low[x]=std::min(low[x],dfn[u]);
	}
	if (dfn[x]==low[x]){
		cnt++;
		int u;
		do{
			u=st[tp--];
			vis[u]=0;
			col[u]=cnt;
		}while (u!=x);
	}
}
ll Abs(ll x){
	return x>0?x:-x;
}
void dfs(int x,int c){
	vis[x]=1;
	for (int i=last[x];i;i=Next[i]){
		int u=to[i];
		if (col[u]!=c) continue;
		if (!vis[u]){
			d[u]=d[x]+w[i];
			dfs(u,c);
		}else {
			ll t=Abs(d[x]-d[u]+w[i]);
			g=gcd(g,t);
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
	}
	for (int i=1;i<=n;i++)
		if (!dfn[i]) tarjan(i);
	for (int i=1;i<=n;i++){
		if (vis[i]) continue;
		g=0;
		dfs(i,col[i]);
		a[col[i]]=g;
	}
	scanf("%d",&Q);
	while (Q--){
		scanf("%d%d%d",&x,&s,&t);
		x=col[x];
		if ((!s) || s%gcd(a[x],t)==0) puts("YES");
		else puts("NO"); 
	}
}
posted @ 2021-05-27 10:23  flyfeather  阅读(77)  评论(0编辑  收藏  举报