1. 程式人生 > 其它 >2021.09.25 周測

2021.09.25 周測

2021.09.25 周測

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+\)