2023.8.25
A
两人轮流从字符串首尾取字符,问按最优策略谁获胜。
保证字符串长度为偶数。多测。
\(\sum |S|\le 2000\).
考虑对于每个区间 \(f_{i,j}\) 直接做。对于 \(s_i\) 和 \(s_j\) 是否相等分讨,容易想出来平凡的转移。
就是我写的比较屎山。
#include<bits/stdc++.h>
#define N 2010
#define inf 2e9
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int T,n;char s[N];
int f[N][N];//0A1D2B
int main(){
T=read();
while(T--){
scanf("%s",s+1),n=strlen(s+1);
for(int i=0;i<=n;i++)f[i+1][i]=1;
for(int len=2;len<=n;len+=2)
for(int i=1,j=i+len-1;j<=n;i++,j++){
if(len==2){
f[i][j]=(s[i]==s[j])?1:0;
continue;
}
f[i][j]=0;
if(s[i]!=s[j]){
if(s[i]<s[j]){
if(s[i+1]<s[i])f[i][j]=2;
if(s[i+1]==s[i])f[i][j]=f[i+2][j];
if(s[i+1]>s[i])f[i][j]=0;
}
else{
if(s[j-1]<s[j])f[i][j]=2;
if(s[j-1]==s[j])f[i][j]=f[i][j-2];
if(s[j-1]>s[j])f[i][j]=0;
}
}
else{
if(s[i+1]<s[i]&&s[j-1]<s[j])f[i][j]=2;
else if(s[i+1]<s[i]&&s[j-1]>s[j])f[i][j]=0;
else if(s[i+1]>s[i]&&s[j-1]<s[j])f[i][j]=0;
else if(s[i+1]>s[i]&&s[j-1]>s[j])f[i][j]=f[i+1][j-1];
f[i][j]=max(f[i][j],f[i+1][j-1]);
if(s[i+1]==s[i])f[i][j]=max(f[i][j],f[i+2][j]);
if(s[j-1]==s[j])f[i][j]=max(f[i][j],f[i][j-2]);
}
}
puts(!f[1][n]?"Alice":(f[1][n]==1?"Draw":"Bob"));
}
return 0;
}
B
一张带权无向图,有两个内鬼初始在 \(x\) 和 \(y\),有 \(k\) 个目标。
此外有 \(e\) 条情报,每条是一个三元组 \((p,x,t)\) 表示目标 \(p\) 在 \(t\) 时刻出现在点 \(x\).
若时间点 \(t\) 有任何一个内鬼在点 \(x\) 那么目标 \(p\) 被捕获。
问内鬼捕获全部目标的最短时间,或报告无法捕获所有目标。多测。
\(\sum n\le 10^4\),\(\sum m\le 2\times 10^4\),\(\sum e\le 10^5\),\(1\le k\le 8\),\(1\le t\le 10^8\).
容易想到 \(f_{S,v}\) 表示抓了 \(S\) 里面的人,当前在 \(v\) 点的最少时间。
对于 “移动” 和 “蹲点” 分别转移,前者即固定 \(S\),把所有 \(v\) 的最短路更新。
看完 std 脑洞大开。
#include<bits/stdc++.h>
#define N 10005
#define M 20010
#define K 1<<8
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define vit vector<int>::iterator
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
const int inf=0x3f3f3f3f;
int T,n,m,k,E,lim;
int head[N],nxt[M<<1],ver[M<<1],dist[M<<1],tot;
void add(int u,int v,int w){
nxt[++tot]=head[u];
ver[tot]=v,dist[tot]=w;
head[u]=tot;
}
vector<int>vec[9][N];
int f[K][N],g[K];
void dijkstra(int *f){
priority_queue<pii>q;
for(int i=1;i<=n;i++)q.push(mp(-f[i],i));
while(!q.empty()){
int u=q.top().se,w=q.top().fi;q.pop();
if(f[u]!=-w)continue;
for(int i=head[u],v;i;i=nxt[i]){
if(f[v=ver[i]]>f[u]+dist[i]){
f[v]=f[u]+dist[i];
q.push(mp(-f[v],v));
}
}
}
}
void solve(int x){
memset(f,0x3f,sizeof(f)),f[0][x]=0;
for(int S=0;S<lim;S++){
dijkstra(f[S]);
for(int i=1;i<=n;i++){
int tim=min(inf,f[S][i]);
for(int j=1;j<=k;j++)
if(!(S&(1<<j-1))){
int tp=*lower_bound(vec[j][i].begin(),vec[j][i].end(),tim);
f[S^(1<<j-1)][i]=min(f[S^(1<<j-1)][i],tp);
}
}
}
}
int main(){
T=read();
while(T--){
n=read(),m=read(),k=read(),lim=(1<<k);
lim=(1<<k),tot=0;
for(int i=1;i<=n;i++){
head[i]=0;
for(int j=1;j<=k;j++)
vec[j][i].clear();
}
for(int i=1,u,v,w;i<=m;i++){
u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
}
E=read();
for(int i=1,p,x,t;i<=E;i++){
p=read(),x=read(),t=read();
vec[p][x].push_back(t);
}
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++){
vec[i][j].push_back(inf);
sort(vec[i][j].begin(),vec[i][j].end());
}
int x=read();solve(x);
for(int S=0;S<lim;S++){
g[S]=inf;
for(int i=1;i<=n;i++)
g[S]=min(g[S],f[S][i]);
}
int y=read();solve(y);
int ans=inf;
for(int S=0;S<lim;S++){
int mn=inf;
for(int i=1;i<=n;i++)
mn=min(mn,f[S][i]);
ans=min(ans,max(mn,g[(lim-1)^S]));
}
if(ans>=inf)printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}
C
2020ICPC·小米 网络选拔赛第一场 E.Phone Network
这也能开出来?
给一个值域 \([1,m]\) 的数列 \(\{a_n\}\),保证每个数至少出现一次。
对于 \(i\in[1,m]\),求出一个最短的区间长度,使得其包含 \([1,i]\) 的所有数。
\(n,m\le 5\times 10^5\).
考虑维护 \(R_{i,l}\) 表示以 \(l\) 为左端点,包含 \([1,i]\) 的最小右端点,考虑从 \(i\) 转移到 \(i+1\).
记数字 \(i+1\) 的位置为 \(p_1,p_2,\dots,p_k\),那么维护 \(R\) 数组就变成了区间 \(\max\).
注意到对于一个 \(i\),\(R_{i,1\sim n}\) 单调,故可以在 \([p_{j-i}+1,p_j]\) 中找到 \(R_{i+1,l}<p_j\) 的一段 \(l\),就变成区间赋值了。
需要维护 \(R_{i,l}-l+1\) 的值,每次取最小值作为答案。
怎么维护 \(\min R_{i,l}-l+1\) 呢?
在线段树的节点上记录这个值,那么区间赋值的使用可以直接用懒标记更新,即:
#include<bits/stdc++.h>
#define N 500010
#define inf 0x3f3f3f3f
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
struct Tree{
int l,r,mn,res,tag;
#define l(x) t[x].l
#define r(x) t[x].r
#define mn(x) t[x].mn
#define res(x) t[x].res
#define tag(x) t[x].tag
}t[N<<2];
#define ls p<<1
#define rs p<<1|1
void build(int p,int l,int r){
l(p)=l,r(p)=r,res(p)=inf;
if(l==r)return;
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void pushup(int p){
mn(p)=min(mn(ls),mn(rs));
res(p)=min(res(ls),res(rs));
}
void pushdown(int p){
if(!tag(p))return;
tag(ls)=tag(rs)=mn(ls)=mn(rs)=tag(p);
res(ls)=tag(p)-r(ls)+1,res(rs)=tag(p)-r(rs)+1;
tag(p)=0;
}
void modify(int p,int l,int r,int x){
if(l<=l(p)&&r(p)<=r){
mn(p)=tag(p)=x,res(p)=x-r(p)+1;
return;
}
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(l<=mid)modify(ls,l,r,x);
if(r>mid)modify(rs,l,r,x);
pushup(p);
}
int find(int p,int l,int r){
if(mn(p)>=r||r(p)<l||l(p)>r)return -1;
if(l(p)==r(p))return l(p);
int ans=find(rs,l,r);
return ans==-1?find(ls,l,r):ans;
}
int n,m;
vector<int>pos[N];
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
pos[read()].push_back(i);
build(1,1,n);
for(int i=1;i<=m;i++){
int pre=0;
for(int now:pos[i]){
int p=find(1,pre+1,now);
if(p!=-1)modify(1,pre+1,p,now);
pre=now;
}
if(pos[i].back()<n)
modify(1,pos[i].back()+1,n,inf);
printf("%d\n",res(1));
}
return 0;
}
D
没活了,给大伙整一个经典最短路吧。
严格弱化版:The Classic Problem
弱化版是 *3000 的。
给一个有向图,共 \(m\) 组边,每组边描述一个六元组 \((l_1,r_1,l_2,r_2,a,b)\),表示所有 \(x\in[l_1,r_1]\) 向 \(y\in[l_2,r_2]\) 连一条长为 \(a\cdot 2^b\) 的有向边。
问 \(1\) 到各点的最短路。答案对 \(\rm 1e9+7\) 取模。
需要在严格弱化版的基础上加入拆点和线段树优化。