文章目录
The Second Week
一、前言
日拱一卒无有尽,功不唐捐终入海。 ————《法华经·观世音菩萨普门品》
周日川大线上校赛,三人组队十二个小时。玛卡巴卡打得还不错吧感觉。
哎哟周二的cf个人赛,C题做崩了之后整个心态没了,做了俩三个小时,看着D题写不进去,交了16次也是给我整笑了。后来发现错的真的很无语。
周六晚上做了一下上届川大决赛题,哎哟签到题心态做崩了。
二、算法
1.二叉树
typedef struct treeNode
{
int id;
struct treeNode* LChild;
struct treeNode* RChild;
}TREE,*LPTREE;
//结构体建树节点,LP一般代表指针别名
LPTREE createNode(int id,LPTREE l,LPTREE r) {
LPTREE newNode = (LPTREE)malloc(sizeof(TREE));
//malloc函数的作用是申请内存空间并返回给指针变量
newNode->id = id;
newNode->LChild = l;
newNode->RChild = r;
return newNode;
}
//建立一个节点
void insertNode(LPTREE parentNode,LPTREE LChild,LPTREE RChild) {
parentNode -> LChild = LChild;
parentNode -> RChild = RChild;
}
//在空节点下插入左右子节点
void printCurNodeData(LPTREE curDate) {
std::cout << curData->data << endl;
}
//终于打印节点了
void preOrder(LPTREE root) {
if(root != NULL) {
printCurNodeData(root);
preOrder(root->LChild);
preOrder(root->RChild);
}
}
//前序遍历:根左右(递归方式)
void midOrder(LPTREE root) {
if(root != NULL) {
midOrder(root->LChild);
printCurNodeData(root);
midOrder(root->RChild);
}
}
//中序遍历:左根右(递归方式)
void lastOrder(LPTREE root) {
if(root != NULL) {
preOrder(root->LChild);
preOrder(root->RChild);
printCurNodeData(root);
}
}
//后序遍历:左右根(递归方式)
2.贪心
贪心算法就是通过局部最优解达到整体最优解,经典的是选择排序。
<1>(CF set1746 B)
vjudge上的贪心题单,看了别人的代码写的。
题解:
给出t组案例,每组数组a包含n个数字0或1,要求求出最少经过几次运算,可以使得数组a是一个递增数组,每次运算即把ai加到aj上,去除ai。
从左右俩边往中间遍历,注意不要超出边界,每次有不符合递增条件的数字,都进行一次运算,然后继续遍历,直到l>=r。
数组的最小数字是0,但最大数字未必是1。
代码:
#include<iostream>
#include<utility>
using namespace std;
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
long long t;
cin >> t;
while (t--) {
int n;
cin >> n;
int a[n];
int ans = 0;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
int l = 0, r = n-1;
while(l < r) {
while(a[l] == 0 && l < n)l++;
while(a[r] != 0 && r > -1)r--;
if(l >= r)break;
ans++;
l++;
r--;
}
cout << ans << endl;
}
return 0;
}
3.快速幂
const long long mod = 1e9 + 10;
long long quick_pow ( long long base, long long b ) {
long long ans = 1;
while(b) {
if( b & 1 != 0) {
ans = ans * base % mod;
}
base = base * base % mod;
b >>= 1;
}
return ans;
}
<1>(四川大学线上校赛 E)
做了三个小时,但现在网站不知道怎么个事,打不开了,找出了代码但是原题好像暂时看不了了。
题解:
打表可以看出1对应的是3的一次方,111对应的是3的三次方,以及给出的一组数据可以论证。而1000会比111多2,以此类推每一位的1可以代表增加的数字多少,数据过大所以得开快速幂。
代码:
#include<iostream>
const int MOD = 1e9 + 7;
using namespace std;
long long quick_pow(long long b) {
long long res = 1;
long long base = 3;
while(b>0) {
if(b & 1) res *= base;
res %= MOD;
base *= base;
base %= MOD;
b >>= 1;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
string l;
cin >> l;
long long ans = 1;
long long t = 1;
for(int i = 0; i < l.length(); i++) {
if(l[i] == '1') {
long long res = quick_pow(l.length()-1-i);
res += 2;
ans += (t*res);
ans -= t;
//减去一开始的自己
t = t*2;
//每找到一个1会使后面所有情况多出2
t = t % MOD;
ans %= MOD;
}
}
cout << ans << endl;
return 0;
}
4.string函数
之前写过一些,但是又发现了新的函数,导致我天梯赛浪费了一部分时间。
string s2 = s1.erase(0,2);
//s1,s2都会删除索引为0开始的2个字符
string s2 = s1.insert(0,"abc");
//s1,s2都会在索引为0的位置插入“abc”
s1.replace(0,2,"abc");
//把s1中的从0开始长度为2的字符替换成“abc”
string s2 = s1.substr(0,2);
//s2会截取s1从0开始的2个字符
string s2;
s2.assign(s1);
//s1的内容重新赋值给s2
s2.assign(s1,1,3);
//s2的内容为s1从1开始的3个字符
//那跟substr函数好像一样一样的诶...
<1>(团队程序设计天梯赛 L1-5)
别再来这么多猫娘了! 15分
比赛的时候一直t,没办法全过,后来看到群里学长们在讨论才知道,替换词里可能存在违禁词,无限替换循环。题目本身还是很简单的,就不写题解了。下次一定要注意不要直接用替换词!!!尤其是acm赛制!
代码:
#include<iostream>
using namespace std;
string wjc[102];
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) {
cin >> wjc[i];
}
int k;
cin >> k;
string wb;
getline(cin,wb);
getline(cin,wb);
int res = 0;
for(int i = 0; i < n; i++) {
int t = 0;
t = wb.find(wjc[i],t);
while(t != -1) {
wb.erase(t,wjc[i].length());
wb.insert(t,"?|?|");
t = wb.find(wjc[i],t);
res++;
}
}
if(res >= k) {
cout << res << endl;
cout << "He Xie Ni Quan Jia!" << endl;
}
else {
int lt = 0;
lt = wb.find("?|?|",lt);
while(lt != -1) {
wb.erase(lt,4);
wb.insert(lt,"<censored>");
lt = wb.find("?|?|",lt);
}
cout << wb << endl;
}
return 0;
}
5.dfs和bfs
<1>(AcWing 843)
y总的课,学了好几次这俩个算法了,dfs搞得差不多了,能写,bfs还是只懂概念,希望下次可以写出来。
题解:
代码:
#include<iostream>
using namespace std;
char a[10][10];
//储存棋盘的状态
int ls[10];
//储存这一列是否有皇后,可以用bool函数
int zx[20];
//储存这一斜是否有皇后
int yx[20];
//储存另一斜是否有皇后
int hs;
void dfs(int n) {
//n表示遍历到第几行
if(n == hs) {
for(int i = 0; i < n; i++) {
puts(a[i]);
//直接输出a的这一整行
}
puts("");
//换行输出
return ;
//遍历完成
}
for(int i = 0; i < hs; i++) {
if(ls[i] == 0 && zx[i + n] == 0 && yx[hs - n - 1 + i] == 0) {
//全都没有皇后,这一个位置可以放
a[n][i] = 'Q';
ls[i] = zx[i+n] = yx[hs-n-1+i] = 1;
dfs(n + 1);
//下一行
a[n][i] = '.';
ls[i] = zx[i+n] = yx[hs-n-1+i] = 0;
//遍历完成恢复状态
}
else {
a[n][i] = '.';
}
//可以不写的其实,但不写不知道为什么可以实现要求
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> hs;
for(int i = 0; i < hs; i++) {
for(int j = 0; j < hs; j++) {
a[i][j] = '.';
}
}
dfs(0);
return 0;
}
6.博弈论
第一次听说这个名词,这应该就是针对这类型题的吧,俩个人互相算计的这种。题倒是看到过好几道了,做出来的是一个都没有。
<1>(四川大学线上校赛 J)
Alice and Bob
三个人做了一晚上也没做出来,最后还拼命掐点交WA,后来是问了学长之后又看了半天代码,似懂非懂。给他改得顺眼了点,又加了点注释。
题解:
黑板上写下2N个整数,以及一个至少为2的整数M,Alice和Bob轮流以最优策略删除数字,如果删除的数字之和mod M相等就是Bob赢,否则就是Alice赢。
大概思路应该是,如果全是偶数个,或者全都是大于2/m的组合并且组合数是偶数的话就是Bob赢,否则全是Alice赢。
代码:
#include<iostream>
#include<vector>
#include<unordered_map>
#include<utility>
#define int long long
#define double long double
using namespace std;
unordered_map<int, int>hm;
//储存数组中每个数字出现的次数
vector<int>ans;
//储存输入的数字
void fun ( vector<int> pp,int m ) {
int sum = 0;
for (int i = 0; i < pp.size(); i++) {
int x=pp[i];
//vector也可以直接遍历
hm[x]++;
if (hm[x] % 2 != 0) {
sum++;
} else {
sum--;
}
}
//sum判断是否所有数字都是偶数个
if (m % 2 != 0 || sum==0) {
if (sum)cout << "Alice" << endl;
else cout << "Bob" << endl;
return ;
}
//如果sum是0的话一定是Bob赢,如果m是奇数的话一定是Alice赢
pp.clear();
for ( auto[u,v] : hm ) {
if ( v % 2 != 0 ) {
pp.emplace_back ( u % ( m / 2 ));
}
}
//a,b,c,a+m/2,b+m/2,c+m/2的情况
if ( pp.size() / 2 % 2 != 0 ) {
//3m 或者 m 都无法/2m
cout << "Alice" << endl;
return ;
}
else{
sum = 0;
for (int i = 0; i < pp.size(); i++) {
int x = pp[i];
hm[x]++;
if ( hm[x] % 2 != 0) {
sum++;
} else {
sum--;
}
}
//同上思维
if ( sum != 0 ) cout << "Alice" << endl;
else cout << "Bob" << endl;
}
return ;
}
int32_t main() {
int T = 1;
while (T--) {
int n, m;
cin >> n >> m;
for (int i = 0; i < 2 * n; i++) {
int x;
cin >> x;
ans.emplace_back(x);
//与push_back造成的结果相同,但是emplace_back更简便
}
fun(ans,m);
//判断一下谁赢了
}
return 0;
}
7.素数
//开根号法
bool IsPrime (int x) {
if (x <= 1) {
return false;
}
for (int i = 2; i*i <= x; i++) {
//必须有等于号,例如9;
if (x % i == 0) return false;
}
return true;
}
//埃拉托斯特尼筛法
int pri[1000005];
memset(pri,0,sizeof(pri));
int n;
cin >> n;
for (int i = 2; i*i <= n; i++) {
if(pri[i] == 0) {
for (int j = i*i; j <= n; j+=i) {
//i*i以前的数没有必要算,比i小的数字已经遍历完
pri[j] = 1;
}
}
}
<1>(CF The 21st Sichuan University Programming Contest I)
这真的是签到题
服了呀三个人做了大几个小时这题,愣是没跑过去,给我无语住了。至今不知道之前几段代码错在了哪里,服气了。
题解:
在数组a中寻找最大的数字ai满足其所有的质因子都在该数列中,如果不存在就输出-1,直接暴力求解见代码。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int st[1000005] = {0};
int n;
cin >> n;
int a[n];
for(int i = 0; i < n; i++) {
cin >> a[i];
st[a[i]] = 1;
}
sort(a,a+n);
for(int i = n - 1; i >= 0; i--) {
bool stt = true;
if(a[i] == a[i+1])continue;
for (int j = 2; j < a[i]; j++) {
if(a[i] % j == 0) {
bool pq = true;
for (int q = 2; q*q <= j; q++) {
if(j % q == 0){
pq = false;
}
}
if(pq){
if(!st[j]){
stt = false;
}
}
}
}
if(stt){
cout << a[i] << endl;
return 0;
}
}
cout << -1 << endl;
return 0;
}
8.前缀和
一直以为前缀和超简单的,结果这题真就给我绕晕了。
<1>(山东理工ACM D)
会编程的老师
四个小时的比赛三个小时在做这题,然后还没做出来我也是服了。但是它的算法又很简单,不知道这题算是简单还是不简单了。过得人还巨多。
题解:
题目要求给定q次询问字符串xi,在长度为n的字符串s中寻找一个最长的满足xi中每个字符只出现一次的子串,输出最长长度,s只包含九个字符。
利用前缀和思维,储存s中每个位置包含的九个字母的数量,遍历起位置,结尾的位置用二分判断,满足条件的跟前几轮最大res比较,最终输出。具体见注释。
代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<unordered_map>
#include<map>
using namespace std;
int pre[10][10004];
//储存前缀和
map<char,int>vis;
//用了个map方便查找
int main() {
vis['P']=0;
vis['Q']=1;
vis['Y']=2;
vis['S']=3;
vis['Z']=4;
vis['T']=5;
vis['M']=6;
vis['N']=7;
vis['B']=8;
int n,q;
cin >> n >> q;
string s;
cin >> s;
s = ' ' + s;
for (int i = 1; i < s.length(); i++) {
for (int j = 0; j < 9; j++) {
pre[j][i] = pre[j][i - 1];
//储存到第i个位置,每个元素j出现几次
}
pre[vis[s[i]]][i]++;
//当前位置出现的得再加上一
}
while(q--) {
// cout << "si" << endl;
string x;
cin >> x;
int res = 0;
//末位置
for (int i = 1; i < s.length(); i++) {
// cout << "aaa" << endl;
auto check = [&] (int p) {
bool st = true;
for (int j = 0; j < x.length(); j++) {
if(pre[vis[x[j]]][p] - pre[vis[x[j]]][i - 1] >= 2) {
//如果到这个位置特定元素出现不止一次,那就直接放弃这个末尾
st = false;
return st;
}
}
return st;
};//研究了好久,应该是在循环内定义了一个bool函数
// cout << "sto[" << endl;
int l = i, r = s.length() - 1;
int ans = 0;
while (l <= r) {
int mid = l+r >> 1;
if (check(mid)) {
l = mid + 1;
ans = mid;
//满足条件的最后一位就是ans
}
else r = mid - 1;
}//一个二分开始判断
if (ans) {
bool st = true;
for (int j = 0; j < x.length(); j++) {
if(pre[vis[x[j]]][ans] - pre[vis[x[j]]][i - 1] != 1) {
//哦!这里在判断这个结尾够不够,有些特定元素还没出现过,就不可以用这个结尾
st = false;
break;
}
}
if (st) {
res = max (res, ans - i + 1);
//正确就继续啦
// cout << ans << ' ' << i << endl;
}
}
}
cout << res << endl;
}
return 0;
}
三、总结
前俩周比赛比懵了,好吧不找借口了,一旦不强制,我自己也好久不写题解了,实在不喜欢补题,希望之后稍微自律一点。