231017校内赛

T1 暴力操作

pii638J.jpg

题解

确实非常暴力的一道题

首先非常明显的一点在于前半段没有任何贡献,所以只用考虑如何变小后面半段

很容易想到用二分答案来求最小值

那么该如何验证?

有一种错误的想法是算出最大能除几次,然后再每次除剩下一半中的最大值

发现明显不对,有些数要除多次且容易剩下一些花不完的钱

那么我们又可以想到对于一个数如果除多次,那么我们可以找到相应的次方的价格,看周围是否有更小价格的数

但是这样写对于各种条件很不好处理,换一种想法

对于每一个除数进行拆分,并与因数进行比较,求出最小花费

再与比它大的数比较,做一个后缀最小值

如果实现精细,那么这段的复杂度低于 \(\mathcal O(n\log n)\)

就可以很方便的求出每个数需要低于二分值的答案了

#include<bits/stdc++.h>
#define N 1000010
#define ll long long
using namespace std;
int n,m,k,a[N];
ll c[N];
inline ll check(int mid){
	ll sum = 0;mid++;
	for(int i = 1;i<=n;i++) 
		sum+=c[a[i]/mid+1];
	return sum;
}
int main(){
	freopen("opt.in","r",stdin);
	freopen("opt.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>k;
	for(int i = 1;i<=n;i++)
		cin>>a[i];
	sort(a+1,a+n+1);
	for(int i = 1;i<=m;i++)
		cin>>c[i];
	c[1] = 0;
	for(int i = 1;i<=m;i++)
		for(int j = 1;j<=i&&i*j<=m;j++)
			c[i*j] = min(c[i*j],c[i]+c[j]);
	for(int i = m-1;i>=1;i--)
		c[i] = min(c[i],c[i+1]);
	c[m+1] = 1e18;
	for(int i = 1;i<=m;i++){
		int j = 1;
		while(i*j<=m) j++;
		if(i*j>m&&j<=m)
			c[m+1] = min(c[i]+c[j],c[m+1]);
	}
	for(int i = m;i>=1;i--)
		c[i] = min(c[i],c[i+1]);
	n = (n+1)>>1;
	int l = 0,r = a[n];
	while(l<r){
		int mid = l+r>>1;
		if(check(mid)<=k) r = mid;
		else l = mid+1;
	}
	cout<<r;
	return 0;
}

T2 异或连通

piigilj.jpg

题解

\(01 \ \ Trie\)

对于每一个询问二进制拆位构建一颗树,再在树上进行统计,方法如下这一部分我也不是很明白

注意到 \(1\) 条边只会在 \(\log K\) 段区间里存在, 类似使用线段树分治即可, 复杂度 $\mathcal O(n\log V\log n) $。

#include<bits/stdc++.h>
#define N 100010
#define int long long
using namespace std;
struct node{
	int v,ne;
}e[N*30];
int n,m,q,k,cnt,tot = 1,sum,ch[N*30][2],h[N*30],ans[N*30];
int u[N],v[N],w[N],stk[N],top,fa[N],siz[N],pos[N];
void add(int x,int y){
	e[++cnt].v = y;
	e[cnt].ne = h[x];
	h[x] = cnt;
}
int find_(int x){
	return fa[x]==x?x:find_(fa[x]);
}
void union_(int x,int y){
	int fx = find_(x),fy = find_(y);
	if(fx==fy) return ;
	if(siz[fx]>siz[fy]) swap(fx,fy);
	fa[fx] = fy;
	stk[++top] = fx;
	sum+=siz[fx]*siz[fy];
	siz[fy]+=siz[fx];
}
int insert(int x){
	int p = 1;
	for(int i = 29;i>=0;i--){
		int y = (x>>i)&1;
		if(!ch[p][y])
			ch[p][y] = ++tot;
		p = ch[p][y];
	}
	return p;
}
void backup(int x){
	while(top!=x){
		int u = stk[top--],v = fa[u];
		siz[v]-=siz[u];
		sum-=siz[v]*siz[u];
		fa[u] = u;
	}
}
void dfs(int x){
	if(!x) return ;
	int pre = top;
	for(int i = h[x];i;i = e[i].ne)
		union_(u[e[i].v],v[e[i].v]);
	ans[x] = sum;
	dfs(ch[x][0]);
	dfs(ch[x][1]);
	backup(pre);
}
signed main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>q>>k;
	for(int i = 1;i<=n;i++)
		fa[i] = i,siz[i] = 1;
	for(int i = 1;i<=m;i++)
		cin>>u[i]>>v[i]>>w[i];
	for(int i = 1;i<=q;i++){
		int x;cin>>x;
		pos[i] = insert(x);
	}
	for(int i = 1;i<=m;i++){
		int p = (k^w[i]),now = 1;
		for(int j = 29;j>=0;j--){
			int x = (p>>j)&1;
			if((k>>j)&1) add(ch[now][x^1],i);
			now = ch[now][x];
			if(!now) break;
		}
	}
	dfs(1);
	for(int i = 1;i<=q;i++)
		cout<<ans[pos[i]]<<"\n";
	return 0;
}

T3 诡异键盘

piigV00.jpg

题解

一开始可能会考虑 \(AC\) 自动机,但很明显是不对的,复杂度直接爆炸

那么考虑 \(dp\) ,我们需要求出打印出 \(i\) 向后到 \(j\) 需要多少次操作

即我们选择一个 \(S_p\), 将 \(S_p\) 打出, 然后删到只剩 \(S[i \ldots j]\) 这段

我们建立 \(trie\) 树, 枚举 \([i,j]\),暴力在子树中找一个 \(S_i\),这样只需要求出删掉 \(\left| S_i \right| − (j−i+1)\) 个字符的最小次数

这样 \(DP\) 复杂度为 $\mathcal O(\sum\left|S_i\right| +\left| S \right| ^2) $一个 \(trie\) 树节点 的答案只需求一次

容易发现只需要预处理删掉 \([1,K)\) 个字符的最小次数,这是一个类似“同余”最短路,可以在 \(\mathcal O(K^2)\) 的时间解决

由于本质不同的长度为 \(n\) ,所以复杂度其实是 \(\mathcal O( \sqrt{nT}K)\).

#include<bits/stdc++.h>
#define int long long
#define N 5010
#define inf (2e9)
#define M 1000100
using namespace std;
int n,k,cnt = 1,f[N],ch[M][30],fa[M],ans[M],pos[M],val[N],dis[N],que[N],tot = 0;
bool vis[N];
int insert(string s){
	int now = 1;
	for(int i = 0;i<s.size();i++){
		if(!ch[now][s[i]-'a']){
			ch[now][s[i]-'a'] = ++cnt;
			fa[cnt] = now;
		}
		now = ch[now][s[i]-'a'];
	}
	return now;
}
void bfs(){
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[0] = 0;
	for(int i = 0;i<k;i++){
		int u = -1;
		for(int j = 0;j<k;j++)
			if(!vis[j]&&(u==-1||dis[j]<dis[u])) u = j;
		vis[u] = 1;
		for(int j = 1;j<=tot;j++)
			if(dis[u]+val[que[j]]<dis[(u+que[j])%k]) 
				dis[(u+que[j])%k] = dis[u]+val[que[j]];
	}
	return ;
}
int gt(int x){
	int tmp = k-x%k;
	if(tmp>=k) tmp-=k;
	if(dis[tmp]>=inf) return inf;
	return (x+dis[tmp])/k;
}
signed main(){
	freopen("keyboard.in","r",stdin);
	freopen("keyboard.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--){
		memset(val,0x3f,sizeof(val));
		tot = 0;
		for(int i = 1;i<=cnt;i++){
			memset(ch[i],0,sizeof(ch[i]));
			fa[i] = 0;
		}
		cnt = 1;
		cin>>n>>k;string s;
		for(int i = 1;i<=n;i++){
			cin>>s;
			int tmp = s.size();
			val[tmp%k] = min(val[tmp%k],tmp+k);
			pos[i] = insert(s);
		}
		for(int i = 0;i<k;i++)
			if(val[i]<inf) que[++tot] = i;
		bfs();
		memset(ans,0x3f,sizeof(ans));
		for(int i = 1;i<=n;i++){
			int now = pos[i],tmp = 0;
			while(now!=1){
				ans[now] = min(ans[now],gt(tmp)+1);
				tmp++;
				now = fa[now];
			}
		}
		cin>>s;int l = s.size();
		f[l] = 0;
		for(int i = l-1;i>=0;i--){
			int now = 1;
			f[i] = inf;
			for(int j = i;j<l;j++){
				now = ch[now][s[j]-'a'];
				if(!now) break;
				f[i] = min(f[i],f[j+1]+ans[now]);
			}
		}
		if(f[0]>=inf) cout<<"-1\n";
		else cout<<f[0]<<"\n";
	}
	return 0;
}

T4 民主投票

piigG0x.jpg

题解

结果 T4 最简单了

一道树形 \(dp\)

这道题每个点最少需要的票数来成为最多是确定的,我们可以一开始就处理出来 \(min\)

考虑 $ f_{x,s} $ 表示当前子树 \(x\)\(s\) 票时这个内部消化多少票,用二分来求出其他点是否可以满足小于 \(s-1\) 的条件

那么对于子树大小 \(size > min\) 的点一定是可以的,对于 \(size<min\) 的点一定不可以

只有一个 \(size==min\) 需要考虑

那么当 \(f_{1,min} = 0\) 时,每个点都不会超过当前 \(min\)

那么我们考虑先用 \(min-1\) 求出一个答案,如果 \(f_{x,min-1}=0\) 时,\(f_{1,min} = 1\) 显然不能获胜

如果\(f_{x,min-1}\) 到根节点上所有点都为 \(1\) ,那么 \(f_{1,min-1}=0\) ,所以是可以获胜的

总感觉说的不是太清楚,你们看代码理解一下

#include<bits/stdc++.h>
#define N 1001000
using namespace std;
int n,fa[N],siz[N],tmp[N],ans[N];
bool check(int mid){
	for(int i = 1;i<=n;i++) tmp[i] = 0;
	for(int i = n;i>=2;i--){
		tmp[fa[i]]++;
		if(tmp[i]>mid) tmp[fa[i]]+=tmp[i]-mid;
	}
	return tmp[1]<=mid; 
}
int main(){
	freopen("election.in","r",stdin);
	freopen("election.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--){
		cin>>n;
		for(int i = 2;i<=n;i++)
			cin>>fa[i];
		int l = 1,r = n,s = -1;
		while(l<=r){
			int mid = l+r>>1;
			if(check(mid)) s = mid,r = mid-1;
			else l = mid+1;
		}
		for(int i = 1;i<=n;i++) ans[i] = siz[i] = 0;
		for(int i = n;i>=2;i--)
			siz[fa[i]]+=siz[i]+1;
		for(int i = 1;i<=n;i++){
			if(siz[i]>s) ans[i] = 1;
			else if(siz[i]<s) ans[i] = 0;
		}
		for(int i = 1;i<=n;i++) tmp[i] = 0;
		for(int i = n;i>=2;i--){
			tmp[fa[i]]++;
			if(tmp[i]>s-1) tmp[fa[i]]+=tmp[i]-s+1;
		}
		for(int i = 2;i<=n;i++) tmp[i] = min(tmp[i],tmp[fa[i]]);
		for(int i = 2;i<=n;i++)
			if(tmp[1]==s&&tmp[i]==s&&siz[i]==s)
				ans[i] = 1;
		ans[1] = 1;
		for(int i = 1;i<=n;i++)
			cout<<ans[i];
		cout<<"\n";
	}
	return 0;
}
posted @ 2023-10-19 20:24  cztq  阅读(1)  评论(0编辑  收藏  举报