10.29 正睿考試題解
T1
T1 不知道為什麼很多人做不出來,這個題的關鍵就是我不知道殺手在哪裡,所以我只能選一條邊進行走,而殺手時刻知道我在哪裡,所以他可以在我的這條路徑上進行攔截。所以其實我們需要保證我們選的這條路徑,每個點我們都要比殺手到達的早。
先跑一遍殺手的最短路,然後二分答案,每次 check 的時候跑一遍自己的最短路,需要保證不經過殺手能早到的點即可。
一個錯誤的想法是自己只能走最短路樹,這個顯然是錯誤的,可以輕易 hack 掉。
這個題有 \(n\log n\) 的做法,也就是說與權值無關,不進行二分。
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 10010 #define M 500010 using namespace std; const int INF=0x3f3f3f3f; const dd eps=1e-5; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } struct edge{ int to,next,w; inline void Init(int to_,int ne_,int w_){ to=to_;next=ne_;w=w_; } }li[M<<1]; int head[N],tail; int c,I,t,n,m; dd d[N],TimeLimit[N]; bool vis[N]; inline void Add(int from,int to,int w){ li[++tail].Init(to,head[from],w); head[from]=tail; } inline void Init(){ read(n);read(m); for(int i=1;i<=m;i++){ int from,to,w;read(from);read(to);read(w); Add(from,to,w);Add(to,from,w); } read(c);read(I);read(t); fill(TimeLimit+1,TimeLimit+n+1,INF); } dd l=0,r=1000001; struct Node{ int id; dd val; inline Node(){} inline Node(int id,dd val) : id(id),val(val) {} inline bool operator < (const Node &b)const{ return val>b.val; } }; priority_queue<Node> q; inline void Dij(int s,dd v){ fill(d+1,d+n+1,INF); fill(vis+1,vis+n+1,0); if(TimeLimit[s]<=0) return; q.push(Node(s,0));d[s]=0; while(q.size()){ Node top=q.top();q.pop(); if(vis[top.id]) continue; vis[top.id]=1; for(int x=head[top.id];x;x=li[x].next){ int to=li[x].to; dd w=(dd)li[x].w/v; if(d[to]<=d[top.id]+w||TimeLimit[to]<=d[top.id]+w) continue; d[to]=d[top.id]+w;q.push(Node(to,d[to])); } } } inline bool Check(dd mid){ Dij(c,mid);return d[t]!=INF; } int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); Init();Dij(I,1); for(int i=1;i<=n;i++) TimeLimit[i]=d[i]; // printf("if=%d\n",Check(0.6)); while(r-l>eps){ dd mid=(l+r)/2; if(Check(mid)){ // printf("r=%lf\n",r); r=mid; } else l=mid; } if(l>=1000000){return puts("-1"),0;} else printf("%lf\n",l); return 0; }
T2
這個題建圖跑最短路,需要離散化,沒有多少難度。主要難度是能否想到最短路,幸虧前陣子剛做了一個線段樹優化建圖,對最短路建模比較敏感。
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 10010 #define M 500010 using namespace std; const int INF=0x3f3f3f3f; const dd eps=1e-5; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } struct edge{ int to,next,w; inline void Init(int to_,int ne_,int w_){ to=to_;next=ne_;w=w_; } }li[M<<1]; int head[N],tail; int c,I,t,n,m; dd d[N],TimeLimit[N]; bool vis[N]; inline void Add(int from,int to,int w){ li[++tail].Init(to,head[from],w); head[from]=tail; } inline void Init(){ read(n);read(m); for(int i=1;i<=m;i++){ int from,to,w;read(from);read(to);read(w); Add(from,to,w);Add(to,from,w); } read(c);read(I);read(t); fill(TimeLimit+1,TimeLimit+n+1,INF); } dd l=0,r=1000001; struct Node{ int id; dd val; inline Node(){} inline Node(int id,dd val) : id(id),val(val) {} inline bool operator < (const Node &b)const{ return val>b.val; } }; priority_queue<Node> q; inline void Dij(int s,dd v){ fill(d+1,d+n+1,INF); fill(vis+1,vis+n+1,0); if(TimeLimit[s]<=0) return; q.push(Node(s,0));d[s]=0; while(q.size()){ Node top=q.top();q.pop(); if(vis[top.id]) continue; vis[top.id]=1; for(int x=head[top.id];x;x=li[x].next){ int to=li[x].to; dd w=(dd)li[x].w/v; if(d[to]<=d[top.id]+w||TimeLimit[to]<=d[top.id]+w) continue; d[to]=d[top.id]+w;q.push(Node(to,d[to])); } } } inline bool Check(dd mid){ Dij(c,mid);return d[t]!=INF; } int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); Init();Dij(I,1); for(int i=1;i<=n;i++) TimeLimit[i]=d[i]; // printf("if=%d\n",Check(0.6)); while(r-l>eps){ dd mid=(l+r)/2; if(Check(mid)){ // printf("r=%lf\n",r); r=mid; } else l=mid; } if(l>=1000000){return puts("-1"),0;} else printf("%lf\n",l); return 0; }
T3
這個題還是比較有難度的,原題是一個黑題。具體來說,如果我們知道從某個時間開始,第一次被攔截的紅綠燈編號以及從某個紅綠燈結束時開始到最後需要多長時間,我們就可以解決這道題。
考慮第一個部分,不難發現這個東西可以逆序用 Map 動態維護。初始化的時候我們令 \(map[0]=n+1\) 表示所有時間開始都可以。具體看程式碼。
然後第二個部分我們只需要稍微計算一下即可。就是逆序做,然後找到某個紅綠燈開始的下一次被攔截位置,然後加起來即可。具體看程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define int long long #define uint unsigned int #define ull unsigned long long #define N 500010 #define M number using namespace std; const int INF=0x3f3f3f3f; const int mod=2147483647; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } map<int,int> Map; int n,G,R,len[N],Time[N],A,q; inline void Insert(int l,int r,int id){ if(l==r) return; auto lit=Map.lower_bound(l),rit=Map.upper_bound(r); int Last=prev(rit,1)->second; Map.erase(lit,rit);Map[l]=id;Map[r]=Last; } inline int Find(int posi){ posi%=A; auto it=Map.upper_bound(posi); int now=prev(it,1)->second; return now; } inline int Calc(int x,int y){ if(y==n+1) return len[y]-x; return ((len[y]-x+A-1)/A)*A+Time[y]; } inline void Init(){ read(n);read(G);read(R);A=G+R; for(int i=1;i<=n+1;i++) read(len[i]); for(int i=2;i<=n+1;i++){ len[i]+=len[i-1]; } Map[0]=n+1; for(int i=n;i>=1;i--){ int l=(G-len[i]%A+A)%A,r=A-len[i]%A; int posi=Find(r); Time[i]=Calc(len[i],posi); if(l<r) Insert(l,r,i);› else{Insert(0,r,i);Insert(l,A,i);} } } int ans; inline void Solve(){ read(q); for(int i=1;i<=q;i++){ int x;read(x); x^=(ans%mod); int posi=Find(x); printf("%lld\n",(ans=Calc(-x,posi))); } } signed main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); Init(); // for(int i=n;i>=1;i--){ // printf("%lld ",Time[i]); // } Solve(); }
T4
我們考慮如何判斷一個串是否合法,不難發現,那些操作都可以轉化成下面兩個操作:
- 把兩個字元壓入棧。
- 把一個字元壓入棧,進行縮字串,然後再讓另一個字元入棧。
雖然棧的情況可能很多,但其實去掉我們不管的,只有 \(4\) 中情況:
- 當前棧中序列加入 \(1\) 之後能縮成 \(1\)。
- 當前棧中序列加入 \(1\) 之後能縮成 \(0\)。
- 當前棧中序列加入 \(0\) 之後能縮成 \(1\)。
- 當前棧中序列加入 \(0\) 之後能縮成 \(0\)。
上面四種情況就是我們所關心的。然後我們就可以設計 dp 狀態:
\[f_{i,a} \]表示考慮前 \(i\) 個字串,第 \(a\) 個放發是否合法,然後考察整個字串的話,只需要看 \(f_{n-1}\) 即可。
接下來我們考慮計數,通過上面的啟發,再加上字串是不確定的,所以我們設狀態 \(g_{a,b,c,d}\) 分別表示上面 \(4\) 種轉移是否合法。
我們每次考慮兩個數,先考慮兩個字元壓入棧的情況,然後考慮第二種情況,列舉合法情況,轉移即可。
程式碼:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
char t[N],s[N];
int f[N][16],T,len,a[N];
inline int GetTrans(int p1,int p2,int p3){
return t[p1+(p2<<1)+(p3<<2)]-'0';
}
inline int GetState(int p1,int p2){
return 1<<((p1<<1)+p2);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(T);
while(T--){
scanf("%s%s",t,s+1);
int n=strlen(s+1);
memset(f,0,sizeof(f));
f[0][9]=1;
for(int i=2;i<n;i++)
for(a[i-1]=0;a[i-1]<2;a[i-1]++)
for(a[i]=0;a[i]<2;a[i]++){
if(s[i]!='?'&&a[i]!=s[i]-'0') continue;
if(s[i-1]!='?'&&a[i-1]!=s[i-1]-'0') continue;
for(int S=0;S<16;S++){
if(!f[i-2][S]) continue;
int SS=0;
for(int j=0;j<2;j++)
for(int k=0;k<2;k++){
bool pd=(S&GetState(GetTrans(a[i-1],a[i],j),k));
if(GetTrans(0,a[i],j)==k) pd|=(S&GetState(a[i-1],0));
if(GetTrans(1,a[i],j)==k) pd|=(S&GetState(a[i-1],1));
if(pd) SS|=GetState(j,k);
}
(f[i][SS]+=f[i-2][S])%=mod;
}
}
int ans=0;
for(int S=0;S<16;S++){
if(!f[n-1][S]) continue;
for(int j=0;j<2;j++){
if(s[n]!='?'&&s[n]!=j+'0') continue;
if(S&GetState(j,1)) (ans+=f[n-1][S])%=mod;
}
}
printf("%d\n",ans);
}
return 0;
}