无形的博弈
一道博弈题,有必胜策略。
#include <cstdio>
#define int long long
const int MOD = 998244353;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^'0'),c=getchar();
return x*flag;
}
int n;
int qkpow(int a,int b)
{
int res=1;
while(b>0)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
signed main()
{
n=read();
printf("%d\n",qkpow(2,n));
}
十二桥问题
考虑每条关键边,发现我们要做的安排关键边的顺序,包括从哪边进入,哪边出去。
考虑状态压缩,设
d
p
[
s
]
[
i
]
[
0
/
1
]
dp[s][i][0/1]
dp[s][i][0/1]为已经经过的边的状压为
s
s
s,最后从
i
i
i边的 左边
/
/
/右边出去,可以用刷表法更新,状态转移略。
注意
d
i
j
k
s
t
r
a
dijkstra
dijkstra的写法,我因为这个
T
T
T炸了。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define int long long
#define inf (1ll<<59)
const int MAXN = 50005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^'0'),c=getchar();
return x*flag;
}
int n,m,k,tot,cnt,f[MAXN],mp[MAXN];
int a[30],b[30],w[30],key[30];
int ans,dis[30][MAXN],dp[1<<12][13][2];
struct edge
{
int v,c,next;
}e[8*MAXN];
struct node
{
int u,c;
};
template<class T> void write(T x)
{
if(x<0)return (void)(putchar('-'),write(-x));
if(x>9)return (void)(write(x/10),putchar(x%10+'0'));
return (void)putchar(x%10+'0');
}
void add(int x)
{
if(!mp[x])
{
mp[x]=++cnt;
key[cnt]=x;
}
}
void dijkstra(int s,int cur)
{
queue<node> q;
for(int i=1;i<=n;i++)
dis[cur][i]=inf;
q.push(node{s,0});
dis[cur][s]=0;
while(!q.empty())
{
node t=q.front();
q.pop();
if(t.c>dis[cur][t.u]) continue;
for(int i=f[t.u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(dis[cur][v]>t.c+c)
{
dis[cur][v]=t.c+c;
q.push(node{v,t.c+c});
}
}
}
}
signed main()
{
n=read();m=read();k=read();
add(1);
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read();
if(i<=k)
{
a[i]=u;b[i]=v;w[i]=c;
add(u),add(v);
}
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,c,f[v]},f[v]=tot;
}
for(int i=1;i<=cnt;i++)
dijkstra(key[i],i);
ans=(1ll<<60);
for(int s=0;s<(1<<k);s++)
for(int i=1;i<=k;i++)
dp[s][i][0]=dp[s][i][1]=inf;
for(int i=1;i<=k;i++)
{
dp[(1<<i-1)][i][0]=dis[1][b[i]]+w[i];
dp[(1<<i-1)][i][1]=dis[1][a[i]]+w[i];
}
for(int s=0;s<(1<<k);s++)
for(int i=1;i<=k;i++)
for(int j=1;j<=k;j++)
if(!(s&(1<<j-1)))
{
dp[s|(1<<j-1)][j][0]=min(dp[s|(1<<j-1)][j][0],min(dp[s][i][0]+dis[mp[a[i]]][b[j]]+w[j],dp[s][i][1]+dis[mp[b[i]]][b[j]]+w[j]));
dp[s|(1<<j-1)][j][1]=min(dp[s|(1<<j-1)][j][1],min(dp[s][i][0]+dis[mp[a[i]]][a[j]]+w[j],dp[s][i][1]+dis[mp[b[i]]][a[j]]+w[j]));
}
for(int i=1;i<=k;i++)
ans=min(ans,min(dp[(1<<k)-1][i][0]+dis[mp[a[i]]][1],dp[(1<<k)-1][i][1]+dis[mp[b[i]]][1]));
printf("%lld\n",ans);
}
神J上树
0x01 暴力
对于每个询问
(
s
,
t
)
(s,t)
(s,t),只有当
s
s
s是
t
t
t的祖先时才有答案,从
s
s
s暴力跑到
t
t
t,中途维护一个单调递减的单调栈,计算答案即可,由于
n
n
n很小,可以记忆化,时间复杂度
O
(
n
3
)
O(n^3)
O(n3)。
0x02 链
把链当成一个序列来处理,先维护一个单调栈,然后考虑在单调栈上倍增:
n
x
t
[
i
]
[
j
]
=
n
x
t
[
n
x
t
[
i
]
[
j
−
1
]
]
[
j
−
1
]
nxt[i][j]=nxt[nxt[i][j-1]][j-1]
nxt[i][j]=nxt[nxt[i][j−1]][j−1]
a
n
s
[
i
]
[
j
]
=
a
n
s
[
i
]
[
j
−
1
]
+
a
n
s
[
n
x
t
[
i
]
[
j
−
1
]
]
[
j
−
1
]
ans[i][j]=ans[i][j-1]+ans[nxt[i][j-1]][j-1]
ans[i][j]=ans[i][j−1]+ans[nxt[i][j−1]][j−1]
就从
s
s
s"跳上"单调栈,然后在
t
t
t前面的单调栈节点"下车",快速处理答案,时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
0x03 正解
既然链的情况我们解决了,那么就用树链剖分扩展一下解法,如果直接暴力合并线段树,那么时间复杂度还是降不下来,我们要利用好倍增处理好的局部来加速。
上图是有两条链拼在一起的情况,从
s
s
s跳上单调栈,可以加速到转折点,然后从转折点带了一个权值(理解为合并单调栈),我们要找到离转折点最近的权值比带来的权值更小的,然后在这个点"上车",后面就是链的做法了。
现在的问题是在一个无序的序列里找到一个最近的更小值,直接用线段树,先考虑整棵树能不能产生最小值,然后依次从左右分别找。
时间复杂度
O
(
n
log
n
2
)
O(n\log n^2)
O(nlogn2)
#include <cstdio>
int main()
{
//见zz代码
}