1003题
Slipper
题目大意:
给一个具有 n n n 个节点的树和 n − 1 n-1 n−1 条具有权值的边,再提供一个起点和终点,求最短路,额外条件是当两个点的层数刚好相差 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(x⩾1) 个 y ( 1 ⩽ y ⩽ 6 ) y(1 \leqslant y \leqslant 6) y(1⩽y⩽6) 点数的骰子”
接下来 P e a n u t Peanut Peanut(以下简称 P P P )有以下两种选择:
-
挑战 Y Y Y。一旦挑战,游戏结束,每名玩家都打开杯子,如果杯中确实有 x x x个 y y y点数的骰子,则 Y Y Y 获胜,否则P获胜。
-
继续宣称。仅可以宣称
①“杯中有 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(1⩽y1⩽6) 点数的骰子”;
或②“杯中有 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) 点数的骰子。
二人依次进行挑战或宣称,直至挑战开始,游戏结束。
有下面三个特殊规则:(如果有冲突,规则三优先)
-
如果没有人宣称过“杯中有 x x x 个 1 1 1 点数”,那么点数 1 1 1 视为任意点数的骰子。
-
如果一个杯中点数均相同(共 n n n 个骰子),那么视为有 n + 1 n+1 n+1 个该点数的骰子。
-
如果一个杯中的点数均不相同,则视为“杯中有 0 0 0 个骰子”。
两人觉得这个运气游戏很蠢,所以要在一开局双方就知道杯子内的所有骰子情况下玩这个游戏
问:双方都以最佳方式玩,若 Y Y Y 赢,输出 “Win!”,否则输出"Just a game of chance." 。
思路
如图,绿色是正确宣称,红色是错误宣称
容易发现只要做出错误宣称会直接输掉(对方进行挑战),
做出正确宣称对手则只能继续宣称(对手挑战的话对手就输了)
题目要求是每次宣称都要比原来的宣称更精准,抽象来说就是图上在上一次宣称的右边找一个地方宣称
所以一般情况下,先手会做出个数最多且点数最大的宣称(图中绿色最右边的极限点),
后手只能做出宣称,且只能做出错误宣称,那么先手必赢。
但是当规则三的情况在两个杯子中同时发生时,例如第一个杯子 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 ,这时图是这样
这时视为两个杯中没有任何骰子,所以任何宣称都是错误宣称
而先手必须做出宣称“两个杯子中有 x ( x ⩾ 1 ) x(x \geqslant 1) x(x⩾1) 个 y ( 1 ⩽ y ⩽ 6 ) y(1 \leqslant y \leqslant 6) y(1⩽y⩽6) 点数的骰子”
所以无论怎么宣称都不对,后手只要挑战即可胜利,先手必败。
代码
#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();
}