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 状态:
表示考虑前 \(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;
}