30号个人赛

发布时间 2023-07-31 20:39:41作者: mostimali

比赛链接: https://www.luogu.com.cn/contest/121860#description



A - KUTEVI

解题思路

一道初见比较难入手的题, 觉得一时间找不到合适的算法, 但是仔细观察之后会发现他的题目描述和完全背包有些相似, 而问法和之前做过的一道布尔类型的dp题很像; 所以我们状态表示dp[i]表示能否凑到角度i, i可以由i+a减a或者i-a加a获得; 又因为本题角度不能小于0且不能大于360, 所以要对360取模, 而j也要遍历到大于720才行; 具体代码如下;

神秘代码

#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 998244353;
int n, m, x, y;
int a[100], b[100];
int d[4000];
signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) cin >> b[i];
    d[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = a[i]; j <= 1000;j++) {
            d[j % 360] |= d[(j - a[i]) % 360];
            d[j % 360] |= d[(j + a[i]) % 360];
        }
    }
    for (int i = 1; i <= m; i++) {
        if (d[b[i]]) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0; 
}                                                                                      




B - 高手之在一起

解题思路

签到题, 唯一需要注意的是如果我们需要用getline输入, 那我们需要先用getchar()吸收掉前面n和m后面的换行符; 否则getline会直接从缓冲区读入换行符;

神秘代码

#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 1e9 + 7;
map<string, int> mp;
int n, m, idx;
signed main() {
	cin >> n >> m;
	getchar();
	for (int i = 1; i <= n; i++) {
		string s;
		getline(cin, s);
		mp[s]++;
	}
	for (int i = 1; i <= m; i++) {
		string s;
		getline(cin, s);
		if (mp[s]) idx++;
	}
	cout << idx;
	return 0;
}




C - 密文搜索

解题思路

因为密文只要求连续不要求顺序, 那我们可以把所有密文从大到小排序后用map存起来; 然后对资料进行遍历, 滑动地向后取8个字符, 然后把这8个字符也从大到小排序, 看是否是密文即可;

神秘代码

#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 1e9 + 7;
map<string, int> mp;
int n, m, res;
string s;
signed main() {
	cin >> s;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		string str;
		cin >> str;
		sort(str.begin(), str.end());
		mp[str]++;
	}
	for (int i = 0; i < s.size()-7; i++) {
		string str = s.substr(i, 8);
		sort(str.begin(), str.end());
		res += mp[str];
	}
	cout << res;
	return 0;
}




F - 合并序列

解题思路

一道比较简单的trie树的题目; 把所有字符串用trie树存起来后先遍历前缀字符串, 然后再用dfs找到所有后续的字符串即可;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 1e9 + 7;
int s[N][26];
int h[N];
multiset<string> res;
int n, m, idx;
void ins(string str) {
	int p = 0;
	for (int i = 0; i < str.size(); i++) {
		int u = str[i] - 'a';
		if (s[p][u] == 0) {
			s[p][u] = ++idx;
		}
		p = s[p][u];
	}
	h[p]++;
}
void dfs(int p, string str) {
	if (h[p]) {
		for (int i = 1; i <= h[p]; i++) res.insert(str);
	}
	for (int i = 0; i < 26; i++) {
		char c = 'a' + i;
		if (s[p][i]) {
			dfs(s[p][i], str + c);
		}
	}
}
int find(string str) {
	int p = 0;
	for (int i = 0; i < str.size(); i++) {
		int u = str[i] - 'a';
		if (s[p][u] == 0) return 0;
		p = s[p][u];
	}
	dfs(p,str);
}
signed main() {
	cin >> n;
	string str;
	for (int i = 1; i <= n; i++) {
		cin >> str;
		ins(str);
	}
	cin >> str;
	find(str);
	for (string t : res) {
		cout << t << endl;
	}
	return 0;
}




G - 最长的回文 Calf Flac

解题思路

这个题细节思路不难但细节偏多; 首先是要输入带空格和回车的字符串, 因为本文要求保留其中的换行符, 所以在拼接时需要手动加上; 再就是寻找回文串的方法可以用a[i]=a[i-1] || a[i]=a[i-2]; 这表示以第i个字符为回文串的中间右一的位置, 如果回文串长度为偶数, a[i]=a[i-1]; 如果为奇数则a[i]=a[i-2]; 而本题因为其他字符的干扰所以我们需要去筛选找到合适的字符;
在做本题是注意到了一个细节, 对于string s, 如果s="\n"会输出换行, 而s+='\', s+='n'就会输出"\n"而不是换行;

神秘代码

#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 1e9 + 7;
map<string, int> mp;
int n, m, res = 1;
string s, s1;
bool check1(char c) {//检查是否在字母范围内
	if (c >= 'a' && c <= 'z') return true;
	if (c >= 'A' && c <= 'Z') return true;
	return false;
}
int check2(int x) {//往左找合适的字符
	if (x < 0) return -1;
	while (!check1(s[x]) && x - 1 >= 0) x--;
	if (check1(s[x])) return x;
	else return -1;
}
int check3(int x) {//往右找合适的字符
	if (x >= s.size()) return -1;
	while (!check1(s[x]) && x + 1 < s.size()) x++;
	if (check1(s[x])) return x;
	else return -1;
}
bool check4(char a, char b) {//字母大小写被视为相同的字符
	if (a > b) swap(a, b);
	if (a == b) return true;
	else if (a + 32 == b) return true;
	else return false;
}
int find(int l, int r, int idx) {
	while (1) {
		int a = check2(l - 1);
		int b = check3(r + 1);
		if (a == -1 || b == -1) break;
		else {
			if (check4(s[a], s[b])) {
				l = a, r = b;
				idx += 2;
			}
			else break;
		}
	}
	if (idx > res) {
		int len = r - l + 1;
		s1 = s.substr(l, len);
		res = idx;
	}
	return idx;
}
signed main() {
	string t;
	while (getline(cin, t)) s += t + '\n';
	s1 = s[0];
	for (int i = 0; i < s.size(); i++) {
		if (!check1(s[i])) continue;
		int a = check2(i - 1);
		if (a != -1) {
			if (check4(s[i], s[a])) find(a, i, 2);
			int b = check2(a - 1);
			if (b != -1) if (check4(s[i], s[b])) find(b, i, 3);
		}

	}
	cout << res << endl << s1 << endl;
	return 0;
}




H - Prawnicy

解题思路

一道比较典型的区间贪心; 把所有区间按照左端点从小到大排序, 这样遍历时当前区间的左端点就是区间交集的左端点; 然后用堆来维护区间交集的右端点即可; 因为排序后编号会边, 所以我们用结构体来存初识的编号;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 3e6 + 10, mod = 1e9 + 7;;
int n, m, res, idx;
int heap[N];
int p[N], h[N], re[N];
struct str {
	int l, r, id;
}f[N];
bool cmp(str a, str b) {
	return a.l < b.l;
}
void down(int a) {
	int u = a;
	if (2 * a <= idx && heap[u] > heap[2 * a]) u = 2 * a;
	if (2 * a + 1 <= idx && heap[u] > heap[2 * a + 1]) u = 2 * a + 1;
	if (u != a) {
		swap(heap[a], heap[u]);
		down(u);
	}
}
void up(int a) {
	while (a / 2 >= 1 && heap[a] < heap[a / 2]) {
		swap(heap[a], heap[a / 2]);
		a = a / 2;
	}
}
signed main() {
	scanf("%d%d",&n,&m);
	for (int i = 1; i <= n; i++) {
		int a, b;
		scanf("%d%d",&a,&b);
		f[i] = { a,b,i };
	}
	sort(f + 1, f + 1 + n,cmp);
	int l, r;
	for (int i = 1; i <= n; i++) {
		int a = f[i].l, b = f[i].r;
		idx++;
		heap[idx] = b;
		up(idx);
		if (idx == m) {
			int d = heap[1] - a;
			if (d > res) {
				res = d;
				l = a, r = heap[1];
			}
		}
		else  if (idx > m) {
			swap(heap[1], heap[idx]);
			idx--;
			down(1);
			int d = heap[1] - a;
			if (d > res) {
				res = d;
				l = a, r = heap[1];
			}
		}
		
	}
	printf("%lld\n", res);
	int num = 0;
	for (int i = 1; i <= n; i++) {
		int a = f[i].l, b = f[i].r;
		if (a <= l && b >= r&&num<m) {
			re[num++] = f[i].id;
		}
	}
	sort(re, re + num);
	for (int i = 0; i < num;i++) {
		printf("%lld ", re[i]);
	}
	return 0;
}




I - 隐藏口令 Hidden Password

解题思路

这是一道关于字符串最小表示法的模板题, 直接暴力会爆内存; 以"anabana"为例简单说一下最小表示法, 我们让指针i和j分别指向字符串的前2个字符'a'和'n'; 因为'a'<'n', 所以我们需要把j往右移去找其他可能的最小值, 于是j到达了第3个字符'a', 这时我们引进一个偏移量k, 因为i和j所指的字符相同, 所以我们让k=1, 比较i+k和j+k所指字符的大小; 因为此时'b'<'n'所以要将i移位, 因为i~i+k都已经判断完了, 所以我们把i移到i+k+1; 这就是最小表示法的简单理解, 里面还有一些小细节, 如果移位后i=j, 则我们让j++; 为了防止i+k越界, 我们可以用字符串长度取余并且把k限制为小于字符串长度;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 5e6 + 10, mod = 1e9 + 7;
int n, m;
char s[N];
int check(int len) {
    int i, j, k;
    i = 0; j = 1; k = 0;
    while (i < len && j < len && k < n){
        if (s[(i + k) % len] == s[(j + k) % len] && k < len) k++;
        else {
            if (s[(i + k) % len] > s[(j + k) % len])i = i + k + 1;
            else j = j + k + 1;
            if (i == j)j++;
            k = 0;
        }
    }
    return min(i, j);
}
signed main() {
    cin >> n;
    for (int i = 0; i < n; i++)cin >> s[i];
    int res = check(n);
    cout << res << endl;
    return 0;
}