2021.09.05 周测

2021.09.05 周测

A.异或

Problem

何老板给你一有\(n\)个元素的正整数集合\(a\)。他请你进行如下两步操作:

  1. \(a\)的每个子集的元素和;
  2. 将第\(1\)步求出的所有结果异或(\(xor\))起来;

\(1 \leq n \leq 1000, \sum a \leq 2000000\)

Solution

观察到\(\sum a \leq 2000000\),发现当子集和为偶数的时候,\(xor\)的自反性使它对答案无影响,考虑计算子集出现次数

因为每个元素只能用一次,把元素看成物品体积,把和看成容积,裸的背包。

\(f[j]\)表示子集和为\(j\)出现的次数

\(f[j] = f[j]+f[j-a[i]]\)

因为\(xor\)自反性,所以状态可以更改成\(f[j]\)表示子集和为偶数还是奇数,\(1\)为奇

\(f[j]=f[j]\ xor\ f[j-a[i]]\)

时间复杂度为\(O(nm)\),不行,时间裂开,考虑到\(f[j]\)的取值只有\(0,1\),上\(bitset\)优化\(dp\)\(bitset\)上的第\(i\)位对应的是子集和为\(i\)是奇是偶

对于\(j-a[i]\)在位运算中就相当于是左移\(a[i]\)位(原本在第\(j-a[i]\)位上的二进制位会被移到第\(j\)位上,取个\(xor\),得出奇或偶即可)

\(code:\)

#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 1000 + 5;
const int MAX_V = 2000000 + 5;
int n,a;
int ans;
bitset<2000005> f;
int main() {
	scanf("%d",&n);
	f|=1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a);
		sum+=a[i];
		f=(f<<a)^f;
	}
	for(int i=1;i<=sum;i++){
		if(f[i]==1)
			ans^=i;
	}
	printf("%d",ans);
	return ans;
}
/*
.in1
2
1 3
.out1
6
.in2
5
234 357 425 581 717
.out2
400
*/

B.涂色

Problem

何老板把\(n\)颗石头排成一排,石头从左往右编号\(1\)\(n\)。 初始时,何老板给所有石头都涂上了颜色,第\(i\)号石头的颜色为\(C_i\)
何老板可以进行任意次如下操作:
选两颗颜色相同的石头,将它们之间的所有石头都涂成跟这两颗石头一样的颜色。
何老板想知道,可以得到多少种不同的石头涂色方案。

\(1 \leq n,C_i \leq 1,000,000\) \(1 \leq i \leq n\)

Solution

发现一个显然的性质,两个相同颜色的石头相邻,对答案无贡献,可以缩成一个石头。现在,对于所有相同颜色的石子,彼此间都有贡献,且每次选择离自己最近的来更新一定最优!

为什么呢?

我们发现,当石子颜色为\(12131\)这种情况的时候,选择\([1,5]\)等同于选择\([1,3]\)后再选择\([3,5]\),所以正确性得以保证。

\(f[i]\)表示前\(i\)个石子的涂色方案。

\(f[i]=f[i-1]+f[las[c[i]]]\)

\(las[i]\)表示颜色\(i\)上一次出现的位置。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1000000 + 5;
const ll mod = 1e9 + 7; 
int n,las[MAX_N],a[MAX_N];
ll f[MAX_N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[0]=1;
	for(int i=1;i<=n;i++){
		f[i]=(f[i-1]+f[las[a[i]]]*(las[a[i]]!=i-1 && las[a[i]]!=0))%mod;
		las[a[i]]=i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
/*
.in1
5
1
2
1
2
2
.out1
3
.in2
6
4
2
5
4
2
4
.out2
5
*/

C.充电桩

Problem

某地区有\(N\)个小镇,小镇编号\(1\)\(N\)。小镇间有\(M\)条双向道路相连。其中有\(K\)个小镇有电动车的充电桩。

何老板向你提出了\(Q\)个形如“\(x,y,z\)”的问题,表示如果驾驶一辆续航能力为\(z\)的电动车,从\(x\)号小镇出发,能否顺利到达\(y\)号小镇?能输出“\(YES\)”,否则输出“\(NO\)”。
​ 续航能力为\(z\)是指,充满电最多能行驶\(z\)​公里。电动车可以在有充电桩的地方把电充满。

\(1 \leq N,M,Q \leq 200000\)

\(1 \leq c \leq 10000\)

\(1 \leq z \leq 2*10^9\)

数据保证,\(x,y\)均有充电桩。

Solution

发现题目保证了\(x,y\)均有充电桩,于是乎,我们先考虑从任意点\(a\)到任意点\(b\)所需要的最小代价。

仔细思考一下,发现直接从\(a\)走到\(b\)不一定最优。为什么?再仔细思考一下,发现从\(a\)走到\(a'\)再走回\(a\)在走到\(b\)再走到\(b'\)充满电一定不会劣于直接走(\(a'\)表示离\(a\)最近的充电桩)。那么对于任意\((u,v)\)可以到达需要满足\(dis(u',u)+len(u,v)+dis(v,v') \leq z\)

对于\(dis\),我们只需要跑一个多源最短路,求出每个点到充电桩的最短距离即可。

考虑处理询问。

将每条边\((u,v)\),边长替换为\(dis[u]+len[u,v]+dis[v]\)

方法一,在线算法,\(kruksal\)重构树\(+LCA\)

询问\(val[lca(x,y)]\)是否\(\leq z\)即可

方法二,离线算法,\(kruskal\)

将询问按照\(z\)升序排序

一边执行\(kruskal\),一边处理询问即可。

\(code:\)(离线)

#include<bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int MAX_N = 200000 + 5;
int n,k,Q,m,a[MAX_N],ans[MAX_N],dis[MAX_N],fa[MAX_N];
bool vis[MAX_N],mark[MAX_N];
int _find(int x){
	if(fa[x]!=x) fa[x]=_find(fa[x]);
	return fa[x];
}
struct edge{
	int a,b,c;
}e[MAX_N];
struct Ask{
	int x,y,z,id;
}ask[MAX_N];
bool cmp2(Ask a,Ask b){
	return a.z<b.z;
}
int Last[MAX_N],Next[MAX_N<<1],End[MAX_N<<1],len[MAX_N<<1],tot;
inline void addedge(int x,int y,int z){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot,len[tot]=z;}
struct node{
	int x,dis;
};
bool operator < (node a,node b){
	return a.dis>b.dis;
}
priority_queue<node> q;
void dijkstra(){
	for(int i=1;i<=n;i++){
		if(vis[i]){
			dis[i]=0;
			q.push((node){i,0});
		}
		else{
			dis[i]=inf;
		}
	}
	while(q.size()){
		int x=q.top().x;
		q.pop();
		if(mark[x]) continue;
		mark[x]=1;
		for(int i=Last[x];i;i=Next[i]){
			int y=End[i];
			if(!mark[y] && dis[y]>dis[x]+len[i]){
				dis[y]=dis[x]+len[i];
				q.push((node){y,dis[y]});
			}
		}
	}
}
bool cmp(edge a,edge b){
	return a.c<b.c;
}
void kruskal(){
	sort(e+1,e+1+m,cmp);
	sort(ask+1,ask+1+Q,cmp2);
	int j=1;
	for(int i=1,cnt=0;i<=m;i++){
		while(j<=Q && e[i].c>ask[j].z){
			ans[ask[j].id]=(_find(ask[j].x)==_find(ask[j].y));
			j++;
		}
		int x=_find(e[i].a),y=_find(e[i].b);
		if(x!=y){
			fa[x]=y;
		}
	}
	while(j<=Q){
		ans[ask[j].id]=(_find(ask[j].x)==_find(ask[j].y));
		j++;
	}
}
int main(){
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=k;i++){
		scanf("%d",&a[i]);
		vis[a[i]]=1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
		addedge(e[i].a,e[i].b,e[i].c);
		addedge(e[i].b,e[i].a,e[i].c);
	}
	dijkstra();
	for(int i=1;i<=m;i++) e[i].c+=dis[e[i].a]+dis[e[i].b];
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++){
		scanf("%d%d%d",&ask[i].x,&ask[i].y,&ask[i].z);
		ask[i].id=i;
	}
	kruskal();
	for(int i=1;i<=Q;i++){
		if(ans[i]==1) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

D.俄罗斯套娃

Problem

何老板有\(n\)个俄罗斯套娃,编号\(1\)\(n\)
每个套娃从外部看,都有一定的体积,\(i\)号套娃的外部体积为\(A_i\)
每个套娃内部是空的,有一定的容积,\(i\)号套娃的内部容积为\(B_i\)

对于\(i,j\)两个套娃,若\(A_i \leq B_j\)那么\(i\)号套娃可以套在\(j\)号套娃内。此时,\(j\)号套娃剩余的内部体积为\(B_j-A_i\)

何老板套了一些套娃,设这些套娃从内到外编号为\(i_1,i_2,...,i_k\),则剩余体积为\(B_{i_1}+(B_{i_2}-A_{i_1})+\cdots+(B_{i_k}-A_{i_k-1})\)

何老板想选若干套娃套起来,使得剩余的体积要最小,且剩余的其他套娃无法再套上去。设该最小剩余体积为\(MinV\)
问,使得剩余体积为\(MinV\)的不同方案有多少种?请你帮他计算。\(mod 10^9+7\)后再输出。

Solution

因为求剩余体积为\(MinV\)的方案数

考虑\(dp\)

先将套娃按\(A\)排序

\(f[i]:\)\(i\)号套娃在最外面,在剩下\(i-1\)个里选一些套娃,使能得到的最小剩余空间。

\(g[i]:\)\(f[i]\)方案数

\(f[i]=min\{f[j]+B_i-A_j\} (j<i,A_j \leq B_i)\)

\(g[i]=\sum g[j](j<i,A_j \leq B_i,f[i]=f[j]+A_i-B_j)\)

时间复杂度\(O(n^2)\)

考虑优化,将方程移项一下,把关于\(j\)的放在一块。

\(f[i]=min\{f[j]-A_j\}+B_i\)

用前缀和优化来维护\(f[j]-A_j\)的最小值,\(SumMin[j]=min\{f[k]-A_k\} (1 \leq k \leq j)\)

因为\(A\)有序,所以二分查找\([1,i-1]\)\(\leq B_i\)的最右边位置\(p\),选\(SumMin[p]\)进行转移即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAX_N = 200000 + 5;
const int mod = 1e9+7;
const int inf = 1e18;
int n,m,minout=inf,maxin,f[MAX_N],g[MAX_N],s[MAX_N],r[MAX_N];
struct node{
    int in,out;
}a[MAX_N];
int _out[MAX_N];
bool cmp(node a,node b){
    if(a.out==b.out) return a.in<b.in;
    return a.out<b.out;
}
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld",&a[i].out,&a[i].in);
        minout=min(minout,a[i].out);
        maxin=max(maxin,a[i].in);
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++) _out[i]=a[i].out;
    for(int i=1;i<=n;i++){
        if(a[i].in<minout){
            g[i]=1;
        }
    }
    int minn=inf;
    for(int i=1;i<=n;i++){
        int x=upper_bound(_out+1,_out+1+n,a[i].in)-_out-1;
        f[i]=s[x]+a[i].in;
        if(!g[i]) g[i]=r[x];
        int t=f[i]-a[i].out;
        if(t==s[i-1] && i!=1){
            r[i]=(g[i]+r[i-1])%mod;
        }
        else if(t<s[i-1] || i==1){
            r[i]=g[i];
        }
        else r[i]=r[i-1];
        s[i]=min(s[i-1],t);
        if(a[i].out>maxin) minn=min(minn,f[i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(a[i].out>maxin && f[i]==minn){
            ans=(ans+g[i])%mod;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

总结

这次测试,可以说是\(dp\)专练,但我打的不好,\(A,B\)两道签到题只签了\(A\)\(B\)则因为\(A\)\(bitset\)优化\(dp\)就没有往\(dp\)方向思考,而是在想组合计数\(C\)题第一眼没什么思路,想了一会还是没思路就放了,在看\(D\)题时,脑子因为\(B,C\)已经一片浆糊了,连如何找最小剩余体积都没想出来。

但听完评讲后,发现\(C,D\)并不难,\(C\)题需要认真分析题目性质找到解题的关键,而\(D\)题主要是前缀最小值优化\(dp\)或最短路计数+优化建图。好像这种求极值的个数的题目都可以直接在\(dp\)的时候维护方案数?

总的来说,心态还是有点点问题,无法静下心来分析题目性质,这场比赛或许真的应该如教练说的那样人均\(300+\)

posted @ 2021-09-07 22:02  Thermalrays  阅读(66)  评论(0编辑  收藏  举报