2024“钉耙编程”中国大学生算法设计超级联赛(2)
女神的睿智
void solve() {
string s;
cin >> s;
int a = 0, b = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] == s[0]) a++;
if (s[i] == s[4]) b++;
}
if (s[0] == s[4]) cout << s[0] << '\n';
else if (a > b) cout << s[0] << '\n';
else if (a < b) cout << s[4] << '\n';
else cout << "N\n";
}
URL划分
思路:
找出每个子结构中A=B的形式,且A和B非空
void solve() {
string s;
cin >> s;
for (int i = 0; i < s.size(); ++i) {
if (s[i] == ':' && s[i + 1] == '/' && s[i + 2] == '/') {
cout << '\n';
int t = i + 3;
while (s[t] != '/') cout << s[t ++];
cout << '\n';
string a, b;
bool ok = false;
for (int j = t + 1; j < s.size(); ++j) {
if (s[j] == '/') {
if (!a.empty() && !b.empty()) {
cout << a << '=' << b << '\n';
}
a.clear(), b.clear();
ok = false;
continue;
}
if (s[j] == '=') {
ok = true;
continue;
}
if (ok) b.push_back(s[j]);
else a.push_back(s[j]);
}
return ;
}
cout << s[i];
}
}
传奇勇士小凯
思路:
首先要知道期望次数为E(x)= 1 / p
对于所有的概率就可以知道每个点的期望次数为15 / pi
并且pi的范围为1到15,为了方便计算可以考虑通分,将分母都转化为为1到15的最小公倍数,这样只用计算分子,最后的答案再分子分母同时通分即可
知道了每个点的期望次数,用dijkstra的方法维护最长路即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353, Mod = 1e9 + 7, inf = 1e18;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void solve() {
int n;
cin >> n;
vector<vector<int> > ve(n + 1);
int lx = 1;
for (int i = 2; i <= 15; ++i) {
lx = lcm(lx, i);
}
vector<int> h(n + 1);
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
ve[u].push_back(v), ve[v].push_back(u);
}
for (int i = 1; i <= n; ++i) {
cin >> h[i];
h[i] = lx / h[i] * 15;
}
priority_queue<PII> q;
vector<int> st(n + 1), dis(n + 1, 0);
q.push({h[1], 1});
dis[1] = h[1];
while (q.size()) {
auto [d, u] = q.top();
q.pop();
if (st[u]) continue;
st[u] = 1;
for (auto v:ve[u]) {
if (!st[v] && dis[v] < dis[u] + h[v]) {
dis[v] = dis[u] + h[v];
q.push({dis[v], v});
}
}
}
int ans = 0;
for (int i = 1; i <= n; ++i) ans = max(ans, dis[i]);
int d = gcd(lx, ans);
cout << ans / d << '/' << lx / d << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
鸡爪
思路:
一个鸡爪包含一个点加三条边,需要答案的字典序最小,说明要尽可能选择小的点
有n条边,说明最多有n/3个鸡爪,那么一定会消耗n/3个点,贪心的想就是前n/3个点,编号小的点连的边一定是更优的,现在考虑怎么让编号大的点变得更优,那么就是让最大的点去连最小的3个点
可以发现对于前3个点来说,小于自己的点不足3个,并且4~n/3中的点都已经连了1~3,说明前3个点中需要在大于n/3的点中在选点,那就从n/3 + 1开始选即可
若n%3不为0,说明还多余了一些边,那么用1去连其他点一定是字典序最小的
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e6 + 5, mod = 998244353, Mod = 1e9 + 7, inf = 1e18;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void solve() {
int n;
cin >> n;
int m = n / 3;
set<PII> se;
int now = 2;
for (int i = m; i >= 1; --i) {
int t = 3;
now = 1;
while (t > 0 && now < i) {
se.insert({min(i, now), max(i, now)});
t --, now ++, n --;
}
now = m + 1;
while (t > 0) {
se.insert({min(i, now), max(i, now)});
t --, now ++, n --;
}
}
while (n > 0) {
se.insert({1, now ++});
n --;
}
for (auto [x, y]:se) {
cout << x << ' ' << y << '\n';
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
在 A 里面找有 C 的 B
思路:
kmp+ac自动机
判断c是否在b‘中用kmp,判断b是否在a中用ac自动机
直接用板子就行
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e6 + 5, mod = 998244353, Mod = 1e9 + 7, inf = 1e18, maxn = 5e5 + 5;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int trie[maxn][26]; //字典树
int flag[maxn]; //记录该单词在树中的位置
int fail[maxn]; //失败时的回溯指针
int tot = 0;
int vis[maxn]; //记录该点是否访问过
void insertWords(string s, int id){
int root = 0;
for(int i=0;i<s.size();i++){
int next = s[i] - 'a';
if(!trie[root][next])
trie[root][next] = ++tot;
root = trie[root][next];
}
flag[id] = root;
}
void getFail(){
queue <int>q;
for(int i=0;i<26;i++){ //将第二层所有出现了的字母扔进队列
if(trie[0][i]){
fail[trie[0][i]] = 0;
q.push(trie[0][i]);
}
}
//fail[now] ->当前节点now的失败指针指向的地方
// trie[now][i] -> 下一个字母为i+'a'的节点的下标为trie[now][i]
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=0;i<26;i++){ //查询26个字母
if(trie[now][i]){
//如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
//有点绕,为了方便理解特意加了括号
fail[trie[now][i]] = trie[fail[now]][i];
q.push(trie[now][i]);
}
else//否则就让当前节点的这个子节点
//指向当前节点fail指针的这个子节点
trie[now][i] = trie[fail[now]][i];
}
}
}
void query(string s){
int now = 0;
for(int i=0;i<s.size();i++){ //遍历文本串
now = trie[now][s[i]-'a']; //从s[i]点开始寻找
for(int j=now;j && !vis[j];j=fail[j]){
//一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
vis[j] = 1; //将遍历国后的节点标记,防止重复计算
}
}
}
vector<int> ne;
void init(string pattern) {
int m = pattern.size();
ne = vector<int> (m, 0);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && pattern[i] != pattern[j]) {
j = ne[j - 1];
}
if (pattern[i] == pattern[j]) {
j++;
}
ne[i] = j;
}
}
//是否匹配
bool kmp(string text, string pattern) {
int n = text.size(), m = pattern.size();
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && text[i] != pattern[j]) {
j = ne[j - 1];
}
if (text[i] == pattern[j]) {
j++;
}
if (j == m) {
return true;
}
}
return false;
}
void solve() {
tot = 0;
memset(fail, 0, sizeof(fail));
memset(flag, 0, sizeof(flag));
memset(vis, 0, sizeof(vis));
memset(trie, 0, sizeof(trie));
int n;
cin >> n;
string A, C;
cin >> A >> C;
init(C);
string b1, b2;
for (int i = 1; i <= n; ++i) {
flag[i] = -1;
cin >> b1 >> b2;
if (kmp(b2, C)) {
insertWords(b1, i);
}
}
getFail();
query(A);
for (int i = 1; i <= n; ++i) {
if (flag[i] != -1 && vis[flag[i]]) {
cout << i << ' ';
}
}
cout << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
成长,生命,幸福
思路:
手画下图可以发现答案路径上的点经过操作后新生成的点都属于答案路径
一个度为x的点,操作m次后新生成的点有(x - 1) * (2m - 1) + 1
知道了每个点的贡献后就可以dp求最大直径
但是会取模,所以没有办法直接判断
可以发现式子(x - 1) * (2m - 1) + 1 中的(2m - 1)是定值,那么维护(x - 1) 和 1即可
这里对m小于20的情况直接用式子的值做dp,其他情况就维护(x - 1) 和 1做dp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e6 + 5, mod = 998244353, Mod = 1e9 + 7, inf = 1e18;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int ksm(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = res * a % Mod;
b >>= 1;
a = a * a % Mod;
}
return res;
}
void solve() {
int n, m;
cin >> n >> m;
vector<vector<int> > ve(n + 1);
vector<int> in(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
ve[u].push_back(v), ve[v].push_back(u);
in[u] ++, in[v] ++;
}
if (m < 20) {
vector<int> f(n + 1);
int ans = 0;
auto dfs = [&] (int u, int fa, auto dfs) -> void {
for (auto v:ve[u]) {
if (v == fa) continue;
dfs(v, u, dfs);
ans = max(ans, f[u] + f[v] + (in[u] - 1) * (ksm(2, m) - 1) + 1);
f[u] = max(f[u], f[v]);
}
f[u] += (in[u] - 1) * (ksm(2, m) - 1) + 1;
};
dfs(1, 0, dfs);
cout << ans << '\n';
} else {
PII ans;
vector<PII> f(n + 1);
auto dfs = [&] (int u, int fa, auto dfs) -> void {
for (auto v:ve[u]) {
if (v == fa) continue;
dfs(v, u, dfs);
ans = max(ans, {f[u].first + f[v].first + in[u] - 1, f[u].second + f[v].second + 1});
f[u] = max(f[u], f[v]);
}
f[u].first += in[u] - 1;
f[u].second ++;
};
dfs(1, 0, dfs);
cout << (ans.first * ((ksm(2, m) - 1 + Mod) % Mod) % Mod + ans.second) % Mod << '\n';
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}