2021.09.25 周測
2021.09.25 周測
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+\)