2023牛客多校第二场 - D E F G I K
比赛地址:传送门
赛时前一小时坐大牢~ 后期发力 最后过五题
D 贪心
E 简单数学构造
F 博弈 不会
G 类manacher算法可解 有难度需理解
I 字符串构造
K 简单动态规划
D The Game of Eating
题意
n 个人,m 道菜,n 个人轮流点 k 道菜。每个人都知道所有人对于每道菜的喜爱程度,点了的菜由所有人共享,不能点相同的菜。每个人都想最大化自己的喜爱程度之和,问你最终点菜的集合?
思路
贪心
正向贪心会发现,每个人优先把自己最喜爱的选择了,但是这道菜可能也是别人最喜欢的,由个人收益最大化我们可以知道,后面的人一定不会放弃点这道菜,那么前面的人点了,相当于浪费了一次自己的机会,这样无法达到最优解。那么前面的人其实贪心的想,因为已知所有人对于所有菜品的喜爱程度,所以可以将后面人必选的也是自己收益最大的菜品选择空出来让后面的人选,自己选择剩下的选项中喜爱程度最大的一种,即为自己的最优解。
所以为了不浪费点菜,又让后面的人点到自己喜欢的,那我们可以考虑从后往前安排点菜。从后往前不重复的点点菜人喜爱程度最高的菜。后面选最优对前面做选择的影响是正向的,就是前面点菜的人再点自己目前能选择的喜爱程度最高的菜是与后面不冲突的,这样就消除了从前往后不能最大化收益的影响。
有点妙?这个从后往前的思路?其实就是贪心的运用!
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm = 2e5+5, inf = 0x3f3f3f3f, mod = 998244353;
void solve(){
int n, m, k;
cin >> n >> m >> k;
priority_queue<pii> q[n + 1];
pii t;
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= m; ++ j){
cin >> t.first;
t.second = j;
q[i].push(t);
}
}
vector<int> ans, vis(m + 1, 0);
for(int i = k; i >= 1 ; -- i){
int v = (i - 1) % n + 1;
while(!q[v].empty()){
t = q[v].top();
q[v].pop();
if(vis[t.second] == 0){
ans.push_back(t.second);
vis[t.second] = 1;
break;
}
}
}
sort(ans.begin(), ans.end());
for(auto c : ans){
cout << c << ' ';
}
cout << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
cin >> _;
while(_--){
solve();
}
return 0;
}
E Square
题意
给你一个数 x,让你在1e9范围内找到一个整数 y,满足 \(y^2\) 在数字上以 x 为开头
思路
枚举 \(y^2\) 的位数,开根判断即可。我写的比较丑
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm = 2e5+5, inf = 0x3f3f3f3f, mod = 998244353;
ll base[11];
void pre(){
base[0] = 1;
for(int i = 1; i <= 10; ++ i){
base[i] = base[i - 1] * 10;
}
return ;
}
ll solve(ll x){
if(x == 0) return 0;
ull t, up, temp;
temp = x;
t = 9;
ll len = log10(x) + 1;
while(temp <= 1e18){
ll y = sqrt(temp);
for(ll i = y - 1; i <= y + 1; ++ i){
up = i * i;
if(up == 0) continue;
ll lenup = log10(up) + 1, cha;
cha = lenup - len;
if(cha <= 0) cha = 1;
if(up / base[cha] == x){
return i;
}
}
temp *= 10;
}
return -1;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _ = 1;
pre();
cin >> _;
while(_--){
ll x, ans;
cin >> x;
ans = solve(x);
cout << ans <<'\n';
}
return 0;
}
F Link with Chess Game 待补充
题意
有一条长为 n 的链,r g b三个棋子,双方轮流移动一颗棋子,要求三个棋子构成的位置三元组不能曾经出现,谁移动出曾经出现的一步视为败。问最终谁赢?
思路
具体题面见牛客
二分图博弈?最大匹配?
sorry,没学过qwq
下的代码为结论摘记。。。
代码
void solve(){
int n, r, g, b;
cin >> n >> r >> g >> b;
if((r + g + b) % 2 && n % 2 ) cout << "Bob\n";
else cout << "Alice\n";
return ;
}
G Link with Centrally Symmetric Strings 思路再理解理解
题意
给你一个字符串 S,判断其是否是由若干个中心对称子串拼接而成?
一个字符串是中心对称字符串当且仅当其满足以下的情况之一:
- S = ""
- S = o | s | x | z, 就是说 S 是前面所给的四种字符中的一种
- S = bSq | qSb | dSp | pSd | nSu | uSn | oSo | sSs | xSx | zSz,就是说 S 的首尾是相应的中心对称字符,中间部分是中心对称字符串
思路
中心对称,和轴对称其实相同,就是在轴对称判断相等时修改判断即可,所以本题可以联想manacher算法
那么我们先考虑怎么划分字符串,使得其成为几个中心对称字串拼接?动态规划!就是转移即为中心对称字串转移,之后判断字符串头开始能否转移到尾即可。官方题解说这个想法存在很多\(O(n \log n)\)的做法,我不知道,但是题目给的范围字符串长度可达1e6,所以想要一个线性的做法,其实就是在考虑有没有能够优化的地方。我们将中心对称字符串作为转移的依据,那么我们怎么找中心对称字符串,应该容易联想到manacher算法。
我们先考虑普通的manacher算法处理之后我们能得到什么?每个处理后的字符的最长中心对称半径。对于本题,我们可以知道有些字符不能作为对称中心,所以这里是第一个需要处理的点。所以在进行 p[i] 初赋值的时候,写的是 \(mx > 1\),而不是传统的\(mx > i\),目的就是为了,在之前的最大回文半径没有覆盖到当前位置的时候,判断当前字符串能否作为回文中心。(思路来自carry的提交代码,不知表述是否完善)这里我添了一句if(p[i] < 0){ p[i] = 0; }
就是用于刚说到的判断(\(p[i] = 0\) 时后面的暴力扩展会判断本身,即达到自己之所需)。
之后就是从前往后进行转移。分析后我们可以发现,一个题意字符串可以划分多个最小中心对称字符串的拼接,所以我们直接按照从前往后选择最短的中心对称串转移即可(水平有限,此处描述不详细)。初始 now 位于位置 1 ,如果说遍历的 $i \le now $时,还没到转移的时候,或者是转移半径小于等于 1,此时其无法作为中心对称字符串而进行转移,故直接continue;反之,我们就判断 now 位置是否被新遇到的中心对称字符串包含,包含则对称转移,遍历的 i 也对称转移(节约时间)。
最后判断 now 是否位于处理后字符串的倒数第二个位置(即原串最后的位置)即可。
总结一句就是利用类manacher方法求解最大中心对称子串,线性判断从前到后能否转移
再自己来搓一遍,估计整不出,再理解理解~
还有很多别的判断方法,都可以尝试哈~
题解摘记:
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm = 2e5+5, inf = 0x3f3f3f3f, mod = 998244353;
string ss, tt;
int len;
unordered_map<char, char> q;
vector<int> p;
void init(){
tt = "$#";
for(auto c : ss){
tt += c;
tt += "#";
}
len = 2 * ss.size() + 2;
tt += "!";
p = vector<int>(len + 5, 0);
return ;
}
void manacher(){
int mx = 0, id = 0;
for(int i = 1; i < len; ++ i){
p[i] = mx > 1 ? min(mx - i, p[(id << 1) - i]) : 1;
if(p[i] < 0){ p[i] = 0; }
while(q[tt[i + p[i]]] == tt[i - p[i]]) ++ p[i];
if(i + p[i] > mx){
mx = i + p[i]; id = i;
}
}
return ;
}
void solve(){
cin >> ss;
init();
manacher();
int now = 1;
for(int i = 0; i < len; ++ i){
if(p[i] <= 1 || i <= now) continue;
if(now >= i - p[i] + 1) now = (i << 1) - now , i = now;
}
if(now == len - 1) puts("Yes");
else puts("No");
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
q['o'] = 'o'; q['s'] = 's';
q['x'] = 'x'; q['z'] = 'z';
q['b'] = 'q'; q['q'] = 'b';
q['p'] = 'd'; q['d'] = 'p';
q['n'] = 'u'; q['u'] = 'n';
q['#'] = '#';
int _ = 1;
cin >> _;
while(_--){
solve();
}
return 0;
}
I Link with Gomoku
题意
给你一个 \(n\times m\) 的棋盘,让你构造一个五子棋平局局面
思路
一种构造方法:
形如
"xxxxoxxxxoxxxxo...xxxxoxx..." 末尾不足五位补"x"
"ooooxooooxoooox...ooooxoo..." 末尾不足五位补"o"
"..."
"xoxoxoxoxoxoxoxo...xoxo..."
构造即可
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm = 2e5+5, inf = 0x3f3f3f3f, mod = 998244353;
void solve(){
int n, m;
cin >> n >> m;
string ss[3],tt[3];
tt[0] = "xxxxo";
tt[1] = "oooox";
tt[2] = "xo";
ss[0] = ss[1] = ss[2] = "";
int cnt = m / 5, t = min(3, n);
string ch = "";
for(int i = 0; i < 2; ++ i){
for(int j = 0; j < cnt; ++ j){
ss[i] += tt[i];
}
for(int j = cnt * 5 + 1; j <= m; ++ j){
ch = (i == 0) ? "x" : "o";
ss[i] += ch;
}
}
if(n % 2){
for(int j = 0; j < m/2; ++ j) ss[2] += tt[2];
if(m % 2) ss[2] += "x";
}
for(int i = 1; i < n; ++i){
cout << ss[(i + 1) % 2] << '\n';
}
if(n % 2) cout << ss[2] << '\n';
else cout << ss[1] << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _ = 1;
cin >> _;
while(_--){
solve();
}
return 0;
}
K Box
题意
有 n 个箱子,每个箱子有一个自己的分数 \(a_i\),一些箱子上放了盖子,你最多可以将这个盖子移到当前箱子左边或者右边相邻的箱子上,最多一步。问你最后盖着盖子的箱子的分数和的最大值?
思路
动态规划
定义状态:0 - 当前盖子不移动;1 - 当前盖子左移;2 - 当前盖子右移
状态转移:
当b[i] = 1 时:
0:前一状态的状态2 和 前一状态的状态0、1 + 取当前位置 的最大值
1:前一状态的状态0 和 前一状态的状态1 + 取前一位置 的最大值
2:前一位置的状态0、1、2 + 取后一位置 的最大值
当b[i] = 0 时:
状态的继承即可,但是得保留上述描述的状态 1 的转移
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm = 1e6+5, inf = 0x3f3f3f3f, mod = 998244353;
ll n, a[maxm], b[maxm];
void solve(){
cin >> n;
for(int i = 1; i <= n; ++ i){
cin >> a[i];
}
for(int i = 1; i <= n; ++ i){
cin >> b[i];
}
vector<vector<ll>> dp(n + 1, vector<ll>(3, 0));
for(int i = 1; i <= n; ++ i){
if(b[i] == 0){// b[i] = 0 时的继承态
dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][2]));
dp[i][1] = max(dp[i - 1][0], dp[i - 1][1]);
dp[i][2] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][2]));
}else{// b[i] = 1 的传递态
dp[i][0] = max(dp[i - 1][0] + a[i], max(dp[i - 1][1] + a[i], dp[i - 1][2]));
dp[i][1] = max(dp[i - 1][0], dp[i - 1][1] + a[i - 1]);
dp[i][2] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][2])) + a[i + 1];
}
}
cout << max(dp[n][0], max(dp[n][1], dp[n][2])) << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
// cin >> _;
while(_--){
solve();
}
return 0;
}
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17572300.html