牛客CSP-S提高组赛前集训营5

无形的博弈

在这里插入图片描述
一道博弈题,有必胜策略。
在这里插入图片描述

#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][j1]][j1]
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][j1]+ans[nxt[i][j1]][j1]
就从 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代码
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值