Loading

7.30 个人赛

比赛链接: 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;
}




E - 最长前缀 Longest Prefix

解题思路

本题用到了ac自动机和dp, 其实可以不用ac自动机, 但是我因为没学所以特意去看了ac自动机的做法; 需要改动的是要把标记数组h从数量改为子串的长度, 代码如下;
警钟敲烂! 如果提前加个空格让string的下标从一开始, 记得字符串的长度是s.size()-1!!! 找了一上午发现是这错了给我整吐了

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 2e3 + 10, M = 2e6 + 10;
int n, m, idx;
int tr[N][26], h[N], ne[N];
int d[M];
void ins(string str) {
    int p = 0;
    for (int i = 0; i < str.size(); i++) {
        int t = str[i] - 'A';
        if (!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
    }
    h[p] = str.size();
}
void build() {
    queue<int> q;
    for (int i = 0; i < 26; i++) {
        if (tr[0][i]) q.push(tr[0][i]);
    }
    while (q.size()) {
        int t = q.front();
        q.pop();
        for (int i = 0; i < 26; i++) {
            int j = tr[t][i];
            if (!j) tr[t][i] = tr[ne[t]][i];
            else {
                ne[j] = tr[ne[t]][i];
                q.push(j);
            }
        }
    }

}
void check(string s) {
    d[0] = 1;
    for (int i = 1, j = 1; i < s.size(); i++) {
        int t = s[i] - 'A';
        j = tr[j][t];
        for (int p = j; p && !d[i]; p = ne[p]) {
            if (h[p]) d[i] |= d[i - h[p]];
        }
    }
    for (int i = s.size()-1; i >= 0; i--) {
        if (d[i]) {
            cout << i << endl;
            break;
        }
    }
}
signed main() {
    string str;
    string s = " ";
    while (cin >> str) {
        if (str == ".") break;
        ins(str);
    }
    build();
    while (cin >> str) {
        s += str;
    }
    check(s);
    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;
}
posted @ 2023-07-31 20:35  mostimali  阅读(5)  评论(0编辑  收藏  举报