2022暑期杭电第五场

1003题

Slipper

题目链接

题目大意:

给一个具有 n n n 个节点的树和 n − 1 n-1 n1 条具有权值的边,再提供一个起点和终点,求最短路,额外条件是当两个点的层数刚好相差 k k k 时可以花费 p p p 代价在两个点之间移动。

思路:

题目额外条件相当与在相距 k k k 层的两点之间加了一条权值为 p p p 的边,如果要在每两个点之间加边,复杂度为 O ( n 2 ) O(n^2) O(n2) ,可以在两层之间加两个虚拟点来优化,如 i i i 层和 i + 1 i+1 i+1 层之间的虚拟点,连接一条从i层到虚拟点权值为 p p p 的线,再从这个点连一条从点到 i + 1 i+1 i+1 层权值为 0 0 0的点,另一个虚拟点相反同理,这样就可以减少复杂度为 O ( 2 × n ) O(2 \times n) O(2×n).

代码:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const int N = 4e6 + 10;

ll t,n,from,to,k,p,tot = -1;
ll first[N],d[N],maxd;
ll dis[N];
bool vis[N];

struct edge{
	ll v,w,next;
}e[N];

void add(ll u,ll v,ll w){
	e[++tot] = {v,w,first[u]};
	first[u] = tot;
}

void dfs(ll u,ll fa){
    d[u] = d[fa] + 1;
	maxd = max(maxd,d[u]);
	for(ll i=first[u];~i;i=e[i].next){
		if(e[i].v == fa) continue;
		dfs(e[i].v,u);
	}
}

void dij(ll u){
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	priority_queue<PII,vector<PII>,greater<PII>> q;
	dis[u] = 0;
	q.push({0,u});
	while(q.size()){
		PII top = q.top();
		q.pop();
		ll x = top.second;
		if(vis[x]) continue;
		vis[x] = true;
		for(ll i = first[x];~i;i=e[i].next){
			ll j = e[i].v;
			if(dis[j] > dis[x] + e[i].w){
				dis[j] = dis[x] + e[i].w;
				q.push({dis[j],j});
			}
		}
	}
}

int main(){
    ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
    cin >> t;
    while(t--){
        cin >> n;
		tot = -1,maxd = 0,d[0] = -1,d[1] = 0;
		memset(first,-1,sizeof first);
		for(ll i=1;i<n;i++){
			ll u,v,w;
			cin >> u >> v >> w;
			add(u,v,w);
			add(v,u,w);
		}
		cin >> k >> p >> from >> to;
		dfs(1,0);
		for(ll i=1;i<=n;i++){
			if(d[i] != 0)
				add(n+2*d[i]-1,i,p);
			if(d[i] != maxd)
				add(n+2*(d[i]+1),i,p);
			if(d[i]+k <= maxd)
				add(i,n+2*(d[i]+k)-1,0);
			if(d[i]-k >= 0)
				add(i,n+2*(d[i]-k+1),0);
		}
		dij(from);
		cout << dis[to] << endl;
    }
}

1010题

Bragging Dice

题目链接

题目大意

有两名玩家,每名玩家的杯子里有 n n n 个骰子,骰子仅被投一次后有相应的点数。两名玩家轮流, Y a h A H a YahAHa YahAHa(以下简称 Y Y Y)先开始,第一轮中, Y Y Y 可以宣称“两个杯子中有 x ( x ⩾ 1 ) x(x \geqslant 1) x(x1) y ( 1 ⩽ y ⩽ 6 ) y(1 \leqslant y \leqslant 6) y(1y6) 点数的骰子”

接下来 P e a n u t Peanut Peanut(以下简称 P P P )有以下两种选择:

  1. 挑战 Y Y Y。一旦挑战,游戏结束,每名玩家都打开杯子,如果杯中确实有 x x x y y y点数的骰子,则 Y Y Y 获胜,否则P获胜。

  2. 继续宣称。仅可以宣称

①“杯中有 x 1 ( x 1 > x ) x_1 (x_1 > x) x1(x1>x) y 1 ( 1 ⩽ y 1 ⩽ 6 ) y_1 (1 \leqslant y_1 \leqslant 6) y1(1y16) 点数的骰子”;

或②“杯中有 x 2 ( x 2 = = x ) x_2 (x_2 == x) x2(x2==x) y 2 ( y 2 > y ) y_2 (y_2 > y) y2(y2>y) 点数的骰子。

二人依次进行挑战或宣称,直至挑战开始,游戏结束。

有下面三个特殊规则:(如果有冲突,规则三优先)

  1. 如果没有人宣称过“杯中有 x x x 1 1 1 点数”,那么点数 1 1 1 视为任意点数的骰子。

  2. 如果一个杯中点数均相同(共 n n n 个骰子),那么视为有 n + 1 n+1 n+1 个该点数的骰子。

  3. 如果一个杯中的点数均不相同,则视为“杯中有 0 0 0 个骰子”。

两人觉得这个运气游戏很蠢,所以要在一开局双方就知道杯子内的所有骰子情况下玩这个游戏

问:双方都以最佳方式玩,若 Y Y Y 赢,输出 “Win!”,否则输出"Just a game of chance." 。

思路

Y_`DZHB_U@HE8E0_T_693.png

如图,绿色是正确宣称,红色是错误宣称

容易发现只要做出错误宣称会直接输掉(对方进行挑战),

做出正确宣称对手则只能继续宣称(对手挑战的话对手就输了)

题目要求是每次宣称都要比原来的宣称更精准,抽象来说就是图上在上一次宣称的右边找一个地方宣称

所以一般情况下,先手会做出个数最多且点数最大的宣称(图中绿色最右边的极限点),

后手只能做出宣称,且只能做出错误宣称,那么先手必赢。

但是当规则三的情况在两个杯子中同时发生时,例如第一个杯子 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5 第二个 2 , 3 , 4 , 5 , 6 2,3,4,5,6 2,3,4,5,6 ,这时图是这样

3_EHZO8_CW3O201X_I7YGG6.png

这时视为两个杯中没有任何骰子,所以任何宣称都是错误宣称

而先手必须做出宣称“两个杯子中有 x ( x ⩾ 1 ) x(x \geqslant 1) x(x1) y ( 1 ⩽ y ⩽ 6 ) y(1 \leqslant y \leqslant 6) y(1y6) 点数的骰子”

所以无论怎么宣称都不对,后手只要挑战即可胜利,先手必败。

代码

#include<bits/stdc++.h>
using namespace std;
int a[10];
void solve()
{
	bool flag1=0,flag2=0;
	int n,t;
	cin>>n;
	memset(a,0,sizeof(a));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t);
		a[t]++;
		if(a[t]>1)//不是点数各异的情况
			flag1=1;
	}
	memset(a,0,sizeof(a));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t);
		a[t]++;
		if(a[t]>1)
			flag2=1;
	}
	if(flag1||flag2) cout<<"Win!"<<endl;//如果一个骰子都没有
	else cout<<"Just a game of chance."<<endl;
}
int main()
{
	int test;
	cin>>test; 
	while(test--)
	{
		solve();
	}
}

1012题

Buy Figurines

题目链接

题目大意

商店有 m m m 个窗口,有 n n n 个人来买东西,他们每个人都有给出来的时间和买东西花费的时间,他们优先选择人最少的窗口进行排队,有多个人同样少的优先选择编号更小的

思路

我们用优先队列记录一下有哪些人要在什么时候离开,这样在下一个顾客来的时候,我们直接在优先队列中取 t o p top top,就可以得到有哪些顾客要先删掉了。
很明显在优先队列中顾客离开的时间越早,那删除时优先级越高,所以我们还需要 t t t t t ttttt ttttt 数组记录一下顾客离开的时间(其实这里是记录了每个窗口最后一名顾客什么时候离开)。
然后我们还需要快速查找人数最少的窗口,我们可以用 s e t set set 来记录这个窗口,因为 s e t set set 内部是有序排列的,我们直接找第一个就能知道要选那个窗口了
注意不要直接 s . b e g i n ( ) . f i r s t + + s.begin().first++ s.begin().first++ ,这样可能是未定义行为
文字描述可能有点抽象,还是看代码更清楚

代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, b, a) for (int i = a, i##end = b; i >= i##end; --i)
typedef long long LL;
#define int long long
typedef double db;
#define endl '\n'
const int LIM = 2e5 + 10;
typedef pair<int, int> pii;
int n, m;
int cnt[LIM]; // 窗口的人数
int ttttt[LIM]; // 窗口最后一位顾客离开的时间
pii cus[LIM];  // first: 顾客来的时间 second:顾客花费的时间
set<pii> s;    // first: 窗口的人数 second: 窗口的编号
               //  begin为窗口人数最少的,如果有相同人数,那就是窗口编号最小的
void solve() {
    priority_queue<pii, vector<pii>, greater<pii>>
        q;  // first: 删除时间(顾客离开时间) second:窗口编号 堆顶为删除时间最小的
    cin >> n >> m;
    rep(i, 1, n) cin >> cus[i].first >> cus[i].second;
    sort(cus + 1, cus + 1 + n);
    rep(i, 1, m) s.insert({cnt[i] = 0, i}); // 初始化窗口 
    rep(i, 1, n) {
        while (!q.empty() && q.top().first <= cus[i].first) { // 先删掉该走的顾客
            int& t = cnt[q.top().second]; // 这位顾客的窗口的人数
            s.erase({t, q.top().second}); // 更改这个窗口的人数
            s.insert({--t, q.top().second}); 
            q.pop(); // 删除顾客
        }
        int idx = s.begin()->second; // 获得当前顾客选择的窗口下标
        s.erase({cnt[idx], idx}); // 更改这个窗口的人数
        s.insert({++cnt[idx], idx});
        ttttt[idx] = max(ttttt[idx], cus[i].first) + cus[i].second; // 设置这个窗口最后一个人离开的时间
        q.push({ttttt[idx], idx}); // 放入堆中,以便之后进行删除操作
    }
    cout << *max_element(ttttt + 1, ttttt + 1 + m) << endl;
    rep(i, 1, m) cnt[i] = ttttt[i] = 0;
    s.clear();
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    rep(i, 1, t) solve();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值