2021.09.05 周测
2021.09.05 周测
A.异或
Problem
何老板给你一有\(n\)个元素的正整数集合\(a\)。他请你进行如下两步操作:
- 求\(a\)的每个子集的元素和;
- 将第\(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+\)