CF1340A Nastya and Strange Generator
如題目所定義地,我們設\(r_j\)表示\(\geq j\)的位置中,第一個沒有被占用的位置。特別地,我們認為位置\(n+1\)永遠不被占用。即:如果\([j,n]\)都已經被占用了,那么\(r_j=n+1\)。
題目還定義了,\(\text{count}_t\) 表示有多少個 \(j\) 的 \(r_j=t\)。要求我們從小到大,每次填的位置必須是 \(\text{count}\) 值最大的位置。
如果\(r_j>j\),我們就從\(j\)向\(r_j\)連一條邊,則該圖成為一個森林。\(\text{count}_t\),就是森林里以\(t\)為根的這棵樹的大小。當我們在\(p\)位置填入一個數后,原本\(r_j=p\)的,就會變為\(r_j\leftarrow r_{p+1}\)。這就相當於把森林里的一整棵樹,掛到另一棵樹的某個節點下。
可以用並查集維護這個森林。用一個\(\texttt{multiset}\)維護森林里所有樹的大小。從小到大考慮所有數字,設當前數字要被放在位置\(p\),我們直接看以\(p\)為根的樹,大小是否是\(\texttt{multiset}\)里的最大值即可。
時間復雜度\(O(n\log n)\)。
參考代碼:
//problem:A
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=1e5;
int n,a[MAXN+5],pos[MAXN+5],fa[MAXN+5],sz[MAXN+5];
int get_fa(int x){
return (x==fa[x])?x:(fa[x]=get_fa(fa[x]));
}
multiset<int>s;
void union_s(int x,int y){
x=get_fa(x);
y=get_fa(y);
if(x!=y){
fa[x]=y;
s.erase(s.find(sz[x]));
if(y!=n+1)s.erase(s.find(sz[y]));
sz[y]+=sz[x];
if(y!=n+1)s.insert(sz[y]);
}
}
int main() {
int T;cin>>T;while(T--){
cin>>n;
s.clear();
for(int i=1;i<=n;++i)cin>>a[i],pos[a[i]]=i;
for(int i=1;i<=n+1;++i){
fa[i]=i;
if(i<=n)sz[i]=1,s.insert(1);
}
bool fail=false;
for(int i=1;i<=n;++i){
int u=pos[i];
assert(get_fa(u)==u);
if(sz[u]!=*s.rbegin()){
fail=true;
break;
}
union_s(pos[i],pos[i]+1);
}
if(fail)cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
return 0;
}
CF1340B Nastya and Scoreboard
可以預處理一個數組,\(\text{dis}[i][j]\),其中\(0\leq i<2^7\),\(0\leq j\leq9\),表示數字屏的一個數位,從\(i\)這種狀態,變成\(j\)這個數字,需要點亮多少燈管。特別地,如果對於某個燈管,在\(i\)中是\(1\)而在\(j\)所在的狀態中是\(0\),即:從\(i\)變成\(j\)需要熄滅某個燈管,則\(i\)永遠不可能變成\(j\),此時令\(\text{dis}[i][j]=\inf\)。
我們先判斷可行性。做一個DP:設\(dp[i][j]\)為一個\(\texttt{bool}\)型變量,表示考慮了數字屏的后\(i\)位數字(為什么要從后往前DP,下面會講),已經點亮了\(j\)根燈管,是否存在這樣的方案。轉移是比較簡單的,枚舉把第\(i\)位改成數字\(k\) (\(0\leq k\leq9\)),則\(dp[i][j+\text{dis}[a_i][k]]\texttt{|=}dp[i+1][j]\)。容易發現,問題有解當且僅當\(dp[1][K]=\texttt{true}\)。
確定有解后,我們從前向后,依次構造出每一位。維護一個變量\(\text{used}\),表示已經使用了多少燈管。我們貪心地從\(9\)到\(0\)考慮當前位變成幾。當前位,能變成數字\(k\) (\(0\leq k\leq 9\)),當且僅當\(dp[i+1][K-(\text{used}+\text{dis}[a_i][k])]=\texttt{true}\)。確定了當前位能變成哪個數字后,令\(\text{used}\texttt{+=}k\)即可。
現在,大家就很容易明白我們為什么要從后往前DP。因為在構造答案時,是貪心地從左向右構造。所以會需要判斷后若干位,還剩若干次操作時的可行性。
時間復雜度\(O(n^2\cdot 10+n\cdot 10)\)。
參考代碼:
//problem:B
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=2000,INF=1e9;
const string s[10]={"1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};
int n,K,dis[1<<7][10],b[MAXN+5],ans[MAXN+5];
bool dp[MAXN+5][MAXN+5];
string a[MAXN+5];
int main() {
for(int i=0;i<(1<<7);++i){
for(int j=0;j<10;++j){
bool fail=false;
for(int k=0;k<7;++k)if(((i>>k)&1)&&s[j][k]=='0'){fail=true;break;}
if(fail){
dis[i][j]=INF;
continue;
}
for(int k=0;k<7;++k)if(!((i>>k)&1)&&s[j][k]=='1')dis[i][j]++;
}
}
cin>>n>>K;
for(int i=1;i<=n;++i){
cin>>a[i];
for(int j=0;j<7;++j)if(a[i][j]=='1')b[i]|=(1<<j);
}
dp[n+1][0]=1;
for(int i=n;i>=1;--i){
for(int j=0;j<=K;++j)if(dp[i+1][j]){
for(int k=0;k<10;++k){
if(dis[b[i]][k]==INF)continue;
if(j+dis[b[i]][k]<=K)dp[i][j+dis[b[i]][k]]=1;
}
}
}
if(!dp[1][K]){
cout<<-1<<endl;return 0;
}
int used=0;
for(int i=1;i<=n;++i){
int dig=-1;
for(int j=9;j>=0;--j){
if(dis[b[i]][j]==INF)continue;
if(used+dis[b[i]][j]>K)continue;
//cout<<j<<endl;
if(!dp[i+1][K-(used+dis[b[i]][j])])continue;
dig=j;
break;
}
assert(dig!=-1);
ans[i]=dig;
used+=dis[b[i]][dig];
}
for(int i=1;i<=n;++i)cout<<ans[i];cout<<endl;
return 0;
}
CF1340C Nastya and Unexpected Guest
把一個安全島,拆成\(g\)個點。\((i,left)\)表示在到達安全島\(i\)時,綠燈時間還剩\(left\)秒。問題轉化為,在這\(m\cdot g\leq 10^7\)個點之間,求從\(0\)到\(n\)的最短路。
如果\(n\)不是安全島沒有關系。我們每到達一個狀態,就嘗試能不能用剩下的時間,從當前安全島走到\(n\)。若可以走到,就更新答案。
從每個狀態,只需要向相鄰的兩個安全島走。因為走到更遠的安全島必然會經過相鄰的。
在這張圖上做bfs。求出答案。
emmm...我寫着寫着就寫成了spfa。考場上AC了,但復雜度我也不會證明(如果有會證明的神仙請教教我!)。不過卡掉spfa的一般是網格圖。spfa在這種近似一維的線段上應該還是相當優秀的。
update:看了官方題解。我們把每\((g+r)\)秒視為一個時間單位,則兩個節點之間轉移的邊權要么是\(0\),要么是\(1\)。所以做01 bfs即可。時間復雜度嚴格的\(O(n+m)\)。
所謂01 bfs,就是這種邊權只有\(0\), \(1\)的圖上求最短路的方法。具體做法是維護一個雙端隊列,如果邊權為\(0\),就把新節點\(\texttt{push_front}\),否則就\(\texttt{push_back}\)。這樣,相當於在線性的時間里,實現了dijkstra的效果。
參考代碼(spfa寫法):
//problem:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=1e6,MAXM=1e4,MAXG=1000;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,d[MAXM+5],G,R,dis[MAXM+5][MAXG+5];
bool inq[MAXM+5][MAXG+5];
int main() {
cin>>n>>m;
for(int i=1;i<=m;++i)cin>>d[i];
cin>>G>>R;
sort(d+1,d+m+1);
memset(dis,0x3f,sizeof(dis));
queue<pii>q;
if(d[1]!=0){
if(d[1]>G){
cout<<-1<<endl;return 0;
}
if(G-d[1]==0){
dis[1][G]=G+R;
q.push(mk(1,G));
}
else{
dis[1][G-d[1]]=d[1];
q.push(mk(1,G-d[1]));
}
}
else{
dis[1][G]=0;
q.push(mk(1,G));
}
ll ans=INF;
while(!q.empty()){
int u=q.front().fi;
int le=q.front().se;
q.pop();
//cout<<u<<" "<<le<<endl;
inq[u][le]=0;
ll D=dis[u][le];
if(n-d[u]<=le){
ans=min(ans,D+n-d[u]);
//return 0;
}
if(u!=1 && d[u]-d[u-1]<=le){
if(d[u]-d[u-1]==le){
ll nd=D+le+R;
if(nd<dis[u-1][G]){
dis[u-1][G]=nd;
if(!inq[u-1][G]){
inq[u-1][G]=1;
q.push(mk(u-1,G));
}
}
}
else{
ll nd=D+d[u]-d[u-1];
if(nd<dis[u-1][le-(d[u]-d[u-1])]){
dis[u-1][le-(d[u]-d[u-1])]=nd;
if(!inq[u-1][le-(d[u]-d[u-1])]){
inq[u-1][le-(d[u]-d[u-1])]=1;
q.push(mk(u-1,le-(d[u]-d[u-1])));
}
}
}
}
if(u!=m && d[u+1]-d[u]<=le){
if(d[u+1]-d[u]==le){
ll nd=D+le+R;
if(nd<dis[u+1][G]){
dis[u+1][G]=nd;
if(!inq[u+1][G]){
inq[u+1][G]=1;
q.push(mk(u+1,G));
}
}
}
else{
ll nd=D+d[u+1]-d[u];
if(nd<dis[u+1][le-(d[u+1]-d[u])]){
dis[u+1][le-(d[u+1]-d[u])]=nd;
if(!inq[u+1][le-(d[u+1]-d[u])]){
inq[u+1][le-(d[u+1]-d[u])]=1;
q.push(mk(u+1,le-(d[u+1]-d[u])));
}
}
}
}
}
if(ans==INF)cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}
