D.Chocolate
结论:除了1*1的情况,先手必胜。
思路:对于1*n的情况下先手都是必胜,n*m的情况先手总可以让后手先造成1*m或1*n的局面。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
int main()
{
IOS
ll n, m;
cin >> n >> m;
if(n == 1 && m == 1)
{
cout << "Walk Alone";
}
else cout << "Kelin";
return 0;
}
K.Subdivision
题意:给一个图,边权都为1,可以在点与点的边上新加一个点,求距离节点1不超过k的点最多可以有多少个。
思路:对于一条路径1-2-3-4-5,与其在开始或者中间加点,不如在最后一个点与前一个点的边上加点,这样对后继节点造成的影响是最小的。
可以建一颗树,共n-1条边来作为支撑,除了叶子结点和其前驱节点连成的边,其他树边上是不建点的。对于非树边,在两边的节点上加点是不会对其他节点造成任何影响的,非常的nice。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 100010, M = 400010;
int k;
int h[N], e[M], ne[M], idx;
bool st[N];
int p[N], dist[N];//p数组记录前驱节点
stack<int> Q;
ll ans = 1;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void bfs()
{
//st[1] = true;
queue<int> q;
q.push(1);
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + 1)
{
dist[j] = dist[t] + 1;
q.push(j);
Q.push(j);
p[j] = t;
}
}
}
}
void bfs2()
{
while(Q.size())
{
int t = Q.top();
Q.pop();
if(dist[t] > k)continue;
ans ++;
int cnt =0;
for(int i = h[t]; i != -1; i = ne[i])
{
cnt ++;
}
if(cnt == 1)
{
ll tmp = k - dist[t];
ans += tmp;
continue;
}
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > k)continue;
if(p[t] == j || p[j] == t)continue;
ll tmp = k - dist[j];
ans += tmp;
}
}
}
int main()
{
IOS
int n, m;
cin >> n >> m >> k;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
bfs();
bfs2();
cout << ans;
return 0;
}
J. Roulette
赢一次后押1块,输一次赌注加倍。
赢:1 = 1
输赢:-1 + 2 = 1
输输赢:-1 - 2 + 4 = 1
输输输赢:-1 -2 -4 + 8 = 1
由等比数列可以发现输输输输...赢造成的结果是和赢造成结果是一样的,都可以看作是在当前步骤赢,当前步骤输掉的概率就是(1 - 输到破产的概率)
连输多少次才能破产的次数:r = log2(n + 1)
在区间范围内的连输多少次才能破产的次数是相等的,都是r,共计2的r次方个数。
还有逆元可以直接当分数用。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int mod = 998244353;
ll qmi(ll a, ll k)
{
ll res = 1, p = mod;
while(k)
{
if(k & 1)res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
ll inv(ll x)
{
return qmi(x, mod - 2);
}
int main()
{
IOS
ll n, m;
cin >> n >> m;
m += n;
ll ans = 1, inv2 = inv(2);
while(n < m)
{
ll r = __lg(n + 1);//连输多少次破产
ll a = (1 - qmi(inv2, r) + mod) % mod;//概率
ll k = min(m, (1ll << r + 1) - 1); //枚举次数的时候不要用快速幂
ll v = k - n;//次数
ans = ans * qmi(a, v) % mod;
n = k;
}
cout << ans;
return 0;
}
H.Matches
官方图解:
所有情况排列组合不过6种情况,两两一组,共计3组。
可以看出只有两个区间一正序一反序 且有公共区间的时候交换才是有好处的,减少的代价是公共区间长度的两倍。
可以开一个结构体数组,记录左右区间和正序反序的状态,按左端点从左到大的顺序枚举。
如果当前枚举到的区间是正序的,就从已经枚举到的反序区间里选取最靠右的端点(可以用一个变量记录),如果该端点比当前区间左端点大就说明有重叠区域,为min(tmp,r)- l。
如果当前枚举到的区间是反序的,就从已经枚举到的正序区间里选取最靠右的端点(可以用一个变量记录),如果该端点比当前区间左端点大就说明有重叠区域,为min(tmp,r)- l。
ans - 两倍的最大重叠区域就是答案。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef struct
{
int l, r, type;
} Node;
const int N = 1000010;
Node q[N];
int a[N], b[N];
bool cmp(Node A, Node B)
{
return A.l < B.l;
}
int main()
{
IOS
int n;
cin >> n;
ll ans = 0;
for(int i = 1; i <= n; i ++)cin >> a[i];
for(int i = 1; i <= n; i ++)
{
cin >> b[i];
q[i].l = min(a[i], b[i]);
q[i].r = max(a[i], b[i]);
q[i].type = (a[i] > b[i]);//1反序 0正序
ans += abs(a[i] - b[i]);
}
sort(q + 1, q + 1 + n, cmp);
ll tmp = -5e9, res = 0;
for(int i = 1; i <= n; i ++)
{
if(q[i].type == 0)
{
if(tmp <= q[i].l)continue;
ll k = min((ll)q[i].r, tmp);
res = max(res, k - q[i].l);
}
else
{
tmp = max(tmp, (ll)q[i].r);
}
}
tmp = -5e9;
for(int i = 1; i <= n; i ++)
{
if(q[i].type == 1)
{
if(tmp <= q[i].l)continue;
ll k = min((ll)q[i].r, tmp);
res = max(res, k - q[i].l);
}
else
{
tmp = max(tmp, (ll)q[i].r);
}
}
ans -= res * 2;
cout << ans;
return 0;
}