写在开头:
2020年ICPC银川站,现场赛,第三题的字典树没能过,首站打铁,不过这一场也让我看到铜牌其实没有想象中的那么难。
2020年ICPC沈阳站,现场赛,封榜后过两题,铜首,I题原本需要黑题难度的知识推导,被队友找规律撕掉了,这一站之后也算成为了一名ACMer。
2021年CCPC桂林站,由本校举办,打星参加,题目质量很高,由于是打星,轻敌没带字典。第五题的key单词三个人都不认识,遗憾打铁,第四题的冒泡排序为之后的广州站做了铺垫–难以证明解法的真实性。
2021年CCPC广州站,作为2021年正式站的首战,铜尾,最后40分钟过的C题(二分答案+思维贪心),其实第四题也能写(打表斐波那契),和银牌差些时间。如果要讲讲这一站的题目,那只能说,没有金牌的实力就不要去想着证明,跟着感觉走。
2021年ICPC沈阳站.第一场icpc线上赛,打铁,过了四道题,H和L题的难度过大,加上B和J题过题太慢(J题最后一分钟过题),rank245,210枚铜牌。线上赛575个队,这对于罚时的要求很大。
2021年CCPC哈尔滨站,rk144,铁首,据说是因为有三个队没过题,牌子发到143.银牌题细节太多,最终没能t出来
2021年ICPC南京站,打铁,差两题,南京站可以说是诸神之战,原本以为卷不到铜牌区,实际上惨不忍睹。我校基地中原本冲金的队伍(哈尔滨站银牌rk1)也打铁了,这场也是2021年最后一场比赛,全程没有进过铜牌区,残酷。
大二上结束,退役与否还没这么快下结论,但是这一年半时间的算法学习,让我错过了很多风景,不过还好,我的内心仍然充满着热情和好奇,仍然有着踏入新领域的勇气。
也很庆幸,在大一纷繁的节奏中找到了对抗孤独的良药,焦虑,烦躁时,全身心投入的解一道题,de一个bug,便能感受到内心平和。
我们生长在最好的时代,我们生长在最坏的时代,我们能仰望星空,我们要脚踏实地。
2023/2/21 update:
大三上:完成ICPC,CCPC双银牌
2022年ICPC沈阳站 银牌 贡献了一道酒馆战棋的模拟题,比其他人更早发现这道题可做
2022年ccpc绵阳站 银牌 贡献了一道精度题,在队友都不出题的情况下,偷偷写题偷偷交题,一度c。
算法
报错
bool operator <(struct node &b)const
{
return r-l<b.r-b.l;//等号可能会错
}
比赛模板
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define il (i<<1)
#define ir (i<<1)+1
#define pb push_back
const int maxn=1e5+5;
inline char nc() {
static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
inline void read(int &sum) {
char ch=nc();sum=0;while(ch<'0') ch=nc();while(ch>='0') sum=(sum<<3)+(sum<<1)+(ch^48),ch=nc();}
ll qsm(ll a,ll b)
{
ll base=a,ans=1;
while(b>0)
{
if(b%2==1)ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
void solve()
{
}
int main()
{
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--)sovle();
return 0;
}
STL
unordered_mapPostgraduate entrance examinatio
ceil()向上取整
floor()向下取整
__builtin_popcount()
用于一个数字的二进制中有多少个1.
bitset
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 1000000007;
const int MX = 1e3+1;
bitset<100*100*100+1>ans,t;
int main(){
int n;scanf("%d",&n);
ans[0]=1;
while(n--){
t.reset();
int l,r;scanf("%d%d",&l,&r);
while(l<=r){
t|=ans<<(l*l);l++;
}
ans=t;
}
printf("%d",ans.count());
return 0;
}
字符串操作
getline(cin,s);
to_string();
stoi/stol/stoll;
优先队列
priority_queue <int> q;
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素
q.back();//返回q的末尾元素
priority_queue<int> da; //大根堆
priority_queue<int,vector<int>,greater<int> > xiao; //小根堆
pair
震惊,map竟然是用pair 实现的。
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main() {
pair<string, string> p1("sc0301","小杨"); // 方式一,创建一个pair名为p1
pair<string, string> p2 = make_pair("sc0302", "小马"); // 方式二,make_pair函数返回一个用"sc0302"和 "小马"初始化的pair
pair<string, string> p3("sc0303", "小王");
pair<string, string> p4("sc0304", "小何");
map<string, string> m1; // 创建一个空map
map<string, string> m2{
p1,p2,p3,p4 }; // 创建一个包含pair p1、p2、p3、p4的map
map<string, string> m3{
{
"sc0301","小杨"},{
"sc0302", "小马"},{
"sc0303", "小王"},{
"sc0304", "小何"} }; // 效果同上一句
map<string, string>::iterator it1 = m2.begin(); // 得到指向m2首元素的迭代器
map<string, string>::iterator it2 = m2.end(); // 得到指向m2尾元素的下一个位置的迭代器
pair<string, string> p11 = *it1; // 得到m2的首元素{"sc0301","小杨"},这是一个pair
string p1_ID = it1->first; // 得到m2的首元素{"sc0301","小杨"}的fisrt成员,学号
string p1_name = it1->second; // 得到m2的首元素{"sc0301","小杨"}的second成员,姓名
for (auto p : m2) {
cout << "学号:" << p.first << "; 姓名:" << p.second << endl;
}
m1.insert(p1); // 在map中插入已有的pair
m1.insert({
"sc0302", "小马" }); // 插入键值对{ "sc0302", "小马" }
m1.insert(pair<string, string> ("sc0303", "小王")); // 创建一个无名pair对象,并插入到map中
m1.emplace(p1); // 要插入的关键字已在容器中,emplace/insert什么都不做
m1.emplace(pair<string, string>("sc0303", "小王")); // 要插入的关键字已在容器中,emplace/insert什么都不做
map<string, string>::iterator it = m2.find("sc0301"); // 查找关键字为"sc0301"的元素,返回一个迭代器
if (it == m2.end()) {
// 若"sc0301"不在容器中,则it等于尾后迭代器
cout << "未找到!" << endl;
}
else {
pair<string, string> result1 = *it; // 找到了
}
int result2 = m2.count("sc0305"); // 查找关键字为"sc0301"的元素,返回关键字等于"sc0301"的元素数量
if (result2==0) {
cout << "未找到!" << endl;
}
else {
cout << "找到了!" << endl;
}
}
sort排序
struct cmp
{
bool operator()(int a,int b)
{
return dist[a]>dist[b];
}
};
friend <返回类型> <函数名> (<参数列表>);
friend bool operator<(const node &a,const node &b)
{
return a.x<b.x;
}
bool operator<(node &a)const
{
return y<b.y;//y表示当前结构体的值
}
//**优先队列相反**
vector
vector<type> name;
a.clear(); //清空a中的元素
a.back(); //返回a的最后一个元素
a.front(); //返回a的第一个元素
vect.size();返回容器中元素的个数
vect.empty();判断容器是否为空
a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,
如a为1,2,3,4,插入元素后为1,5,2,3,4
vect.resize(num);重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。
vect.resize(num,elem);重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。
vect.push_back(1);//在vector最后添加元素1
a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
vect.pop_back()//删除最后一个元素
vector<int> vect(a,a+10);//将数组a整体插入;
sort(vect.begin(),vect.end());
for(int i=0;i<=b.size()-1;i++)
set
begin() //返回set容器的第一个迭代器
end() //返回set容器的最后一个迭代器
clear() //删除set容器中的所有的元素
empty() //判断set容器是否为空
max_size() //返回set容器可能包含的元素最大个数
size() //返回当前set容器中的元素个数
rbegin //返回的值和end()相同
rend() //返回的值和rbegin()相同
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int a[maxn];
int main() {
int n, q;
cin >> n >> q;
set<int>s;
while (q--)
{
int op, id;
cin >> op >> id;
s.insert(0); s.insert(n);
if (op == 1)s.insert(id);
else
{
set<int>::iterator it;
it = s.lower_bound(id);
int len = *it - *(--it);
cout << len << endl;
}
}
return 0;
}
二分查找
upper_bound:
//在一个已排序的序列中[first, last),返回第一个大于val的元素所在的地址。如果未找到结果,则返回last;
lower_bound:
//在一个已排序的序列[first, last), 返回第一个大于等于val元素的地址,如未找到,则返回last;
binary_search:
//在一个已排序的序列[first, last)中, 判断val是否存在;
unique
3 3 2 5 2 3 -》 3 2 5 2 3 3
python黑科技
eval()函数
可以直接求字符串表达式的值注意:“^”转“**”;
s = input()
s1=""
for i in range(len(s)):
if s[i]=="^" :
s1=s1+"**"
else :
s1=s1+s[i]
print(eval(s1))
常用函数
最 da公约数
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
== __gcd()
快速幂
ll qsm(ll a,ll b)
{
ll base=a,ans=1;
while(b>0)
{
if(b%2==1)ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
动态规划
数位dp
技巧 1:如果是从1->n,那么可以将区间问题转化为f(n)-f(m-1);
技巧2:利用数形结构来分析问题,注意last转移时的条件
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#define ll long long
using namespace std;
const int maxn=20;
ll f[maxn][maxn];
ll K;
void init()
{
for(int i=1; i<=9; i++) f[1][i]=1;
for(int i=2; i<maxn; i++){
for(int j=0;j<=9;j++)f[i][0]+=f[i-1][j];
for(int j=1; j<=9; j++)
{
f[i][j]+=f[i-1][j];
for(int k=j+K; k<=9; k++)
{
f[i][j]+=f[i-1][k];
}
}
}
}
ll dp(ll n)
{
if(!n) return 0;
vector<int>nums;
while(n) nums.push_back(n%10),n/=10;
ll res=0;//方案数
int last=0;
for(int i=nums.size()-1; i>=0; i--)
{
int x=nums[i];//x为当前这位数
// cout<<i<<" "<<x<<" "<<last<<" "<<res<<endl;
if(i==nums.size()-1)
{
for(int j=0; j<x; j++) //要保障比下一位>=上一位,所以从last开始枚举,最多枚举到x,last为上一位,也即最高位,对下一位的枚举是有限制的
res+=f[i+1][j];
last=x;
continue; //左端的节点有i+1个位数(因为第一位的下标是0)
}
if(x>last)
{
res+=f[i+1][last];
for(int j=last+K; j<x; j++)res+=f[i+1][j];
if(x>last&&x<last+K)break;
last=x;
} //如果当前这位数比上一位小,那么后面的都不成立了,直接break退出
else if(x==last)x=last;
else break;//如果能顺利到最后一个数说明树的最右边这一段的每一个数都是小于等于前一位数的,因而++
}
return res;
}
int main(void)
{
ll l,r,L,R;
cin>>l>>r>>K;
init();
cout<<dp(r)-dp(l-1)<<endl;
//cout<<f[4][1]<<endl;
return 0;
}
背包问题
背包问题一定是dp?
no!暴力搜索也能过背包问题,dp类似于记忆化搜索,如何有效利用已经被记录的信息是问题关键,如何保持信息的后续有效性也是关键。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e4;
const int maxm=26;
int v[maxn],w[maxn],n,m,res=0,dp[maxm][maxn];
int dfs(int i,int j)
{
if(dp[i][j]!=-1)return dp[i][j];
if(i==m)
{
return 0;
}
else if(j<v[i])res=dfs(i+1,j);
else
{
res=max(dfs(i+1,j),dfs(i+1,j-v[i])+v[i]*w[i]);
dp[i][j]=res;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=0;i<m;i++)cin>>v[i]>>w[i];
memset(dp,-1,sizeof(dp));
dfs(0,n);
cout<<res<<endl;
return 0;
}
01背包问题
一维数组需要逆序
二维需要对不操作的传递
bag[j]=maxv(bag[j],bag[j-w[i]]+v[i]);
完全背包问题
一维数组需要正序
maxValue[j] = max(maxValue[j], maxValue[j-w[i]] + v[i]);
多重背包问题
本质与01背包没有什么不同
分组背包问题
增加一维表示组别,保证只在这一组中选一个或者不选
数论
同余问题
定义:同余给定整数m,若用m去除两个整数a和b所得的余数相同,称a和b对模m同余,记作 a ≡ b ( m o d m ) a\equiv \ b(mod\ m) a≡ b(mod m)。
定理1 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m)当且仅当 m ∣ ( a − b ) m|(a-b) m∣(a−b)
定理2 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m)当且仅当存在k满足 a = b + k m a=b+km a=b+km
定理3 同余关系是等价关系
定理4 若a, b, c是整数,m是整数,且 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m),则:
a + c ≡ a + c ( m o d m ) a − c ≡ a − c ( m o d m ) a c ≡ b c ( m o d m ) a+c\equiv a+c(mod\ m)\\ a-c\equiv a-c(mod\ m)\\ ac\equiv bc (mod \ m) a+c≡a+c(mod m)a−c≡a−c(mod m)ac≡bc(mod m)
定理5 设a,b,c,d,为整数,m为正整数,若 a ≡ b ( m o d m ) a\equiv b (mod\ m) a≡b(mod m), c ≡ d ( m o d m ) c\equiv d(mod\ m) c≡d(mod m),则
a x + c y ≡ b x + d y ( m o d m ) ( 即可加性 ) a c ≡ b d ( m o d m ) ( 可乘性 ) a n ≡ b n ( m o d m ) ( 可幂性 ) f ( a ) ≡ f ( b ) ( m o d m ) ( f ( x ) 为任意整数多项式 ) ax+cy\equiv bx+dy(mod\ m)(即可加性)\\ ac\equiv bd(mod\ m)(可乘性)\\ a^n\equiv b^n(mod\ m)(可幂性)\\ f(a)\equiv f(b)(mod\ m)(f(x)为任意整数多项式) ax+cy≡bx+dy(mod m)(即可加性)ac≡bd(mod m)(可乘性)an≡bn(mod m)(可幂性)f(a)≡f(b)(mod m)(f(x)为任意整数多项式)
(ps:同余的性质十分稳定,具有线性。
定理6 设a,b,c,d为整数,m为正整数,则
- 若 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m),且d|m,则 a ≡ b ( m o d d ) a\equiv b (mod\ d) a≡b(mod d)。(ps:当两数对m同余,对于m的因子也同余)
- 若 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m),则 g c d ( a , m ) = g c d ( b , m ) gcd(a,m)=gcd(b,m) gcd(a,m)=gcd(b,m)
- a ≡ b ( m o d m i ) , i = 1 , 2 , … , n a\equiv b (mod\ m_i),i=1,2,\dots,n a≡b(mod mi),i=1,2,…,n成立,当且仅当 a ≡ b ( m o d [ m 1 , m 2 , … , m n ] ) a\equiv b (mod[m_1,m_2,\dots,m_n]) a≡b(mod[m1,m2,…,mn]).
定理7 若 a c ≡ b c ( m o d m ) , d = g c d ( c , m ) ac\equiv bc(mod \ m),d=gcd(c,m) ac≡bc(mod m),d=gcd(c,m),则 a ≡ b ( m o d m / d ) a\equiv b (mod \ m/d) a≡b(mod m/d)
扩展欧几里得
贝祖定理:如果a\b是整数,那么一定存在整数x,y使得 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)有解
推论:如果 a x + b y = 1 ax+by=1 ax+by=1有解,那么 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1
int exgcd(int a, int b, int &x0, int &y0)
{
if(!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a%b, y0, x0);
y -= a/b*x;
return d;
}
int main()
{
int a, b;
cin >> a >> b;
int x0, y0;
cout<<exgcd(a, b, x0, y0 )<<endl;
cout<<(x%b+b)%b<<endl;
return 0;
}
该函数的返回值为a和b的最大公因数d,其中x,y为其中一组解。
一次同余方程ax + by = c 有解的充要条件为 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)∣c
当 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)∣c是同余方程有 g c d ( a , n ) gcd(a,n) gcd(a,n)个解
x = x 0 + k ∗ b d x = x_0 +k*\frac{b}{d} x=x0+k∗db
y = y 0 − k ∗ a d y = y_0 -k*\frac{a}{d} y=y0−k∗da
欧拉函数
欧拉函数的定义
$\phi (n) $ 表示在1到n中,与n形成互质关系的数的个数。
欧拉函数的性质
-
当n为质数时, ϕ ( n ) = n − 1 \phi(n)=n-1 ϕ(n)=n−1
-
欧拉函数是积性函数,但不是完全积性函数
只有当 n = p 1 ∗ p 2 n=p_1*p_2 n=p1∗p2 时, ϕ ( n ) = ϕ ( p 1 ) ∗ ϕ ( p 2 ) \phi(n)=\phi(p_1)*\phi(p_2) ϕ(n)=ϕ(p1)∗ϕ(p2)
ϕ ( p q ) = ϕ ( q ) ∗ ϕ ( p ) = ( p − 1 ) ( q − 1 ) \phi(pq)=\phi(q)*\phi(p)=(p-1)(q-1) ϕ(pq)=ϕ(q)∗ϕ(p)=(p−1)(q−1)(RSA算法应用)
-
当n>2时, ϕ ( n ) \phi(n) ϕ(n)为偶数
当n为质数的k次幂时, ϕ ( n ) = p k − p k − 1 = ( p − 1 ) p k − 1 \phi(n)=p^k-p^{k-1}=(p-1)p^{k-1} ϕ(n)=pk−pk−1=(p−1)pk−1
证明,只用当数x为p的倍数时,两者才不互质,易得这类数的数量为 p k − 1 p^{k-1} pk−1。
通过以上的理论可以得到以下公式,
ϕ ( N ) = N ∗ ∏ i = 1 n ( 1 − 1 p i ) \phi(N) = N*\prod_{i=1}^{n}(1-\frac{1}{p_i}) ϕ(N)=N∗i=1∏n(1−pi1)
其中pi为质因子,即每个质因子只算一次。
o(n)的筛法筛出质数的同时求得其函数值
需要用到如下性质:
设p为质数
如果p为x的因数
那么 ϕ ( p ∗ x ) = p ∗ x ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ( 1 − 1 p n ) ) = ϕ ( x ) ∗ p \phi(p*x)=p*x*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...(1-\frac{1}{p_n}))=\phi(x)*p ϕ(p∗x)=p∗x∗(1−p11)∗(1−p21)∗...(1−pn1))=ϕ(x)∗p
如果p不是x的因数,即px互质
那么 ϕ ( p ∗ x ) = ϕ ( p ) ∗ ϕ ( x ) = ϕ ( x ) ∗ ( p − 1 ) \phi(p*x)=\phi(p)*\phi(x)=\phi(x)*(p-1) ϕ(p∗x)=ϕ(p)∗ϕ(x)=ϕ(x)∗(p−1)
phi[1]=1;
for(int i=2;i<=maxn;i++)
{
if(!isprime[i])
{
prime[++cnt]=i;
phi[i]