5.5~5.13
区间DP
以一道模板题目为例----石子合并

不能用贪心,会陷入局部最优解。用DP求解
定义dp[i][j]为合并第i堆到第j堆的最小花费
状态转移方程为:
dp[i][j] = min(dp[i][k] + dp[k+1][j] + w[i][j]),i <= k < j,w[i][j]表示第i堆到第j堆的石子总数,可以用前缀和计算。dp[1][n]就是答案
复杂度O(n3),可以用四边形不等式优化到O(n2)
自顶向下的思路:
计算大区间[i,j]的最优值时,合并它的两个子区间[i][k]和[k+1][j],对所有可能的合并(i <= k < j)采取最优合并。子区间再分解为更小的区间,最小区间[i,i+1]只包含两堆石子
自底向上的编程:
先在小区间进行DP得到最优解,再逐步合并小区间为大区间。以下是code:
const int INF = 0x3f3f3f3f;
int dp[n][n] {}; //C++11特性,集成初始化为0
for(int len = 2; len <= n; len++){ //len为i到j的区间长度
for(int i = 1; i <= n - len + 1; i++){ //区间起点i
int j = i + len - 1; //区间终点j
dp[i][j] = INF; //初始化为INF
for(int k = i; k < j; k++){
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
}
}
}
字符串区间

大致题意:两个长度相等的小写字母字符串A和B。定义一次操作:把A的一个连续字串(区间)都转换为某个字符串(就像用刷子刷成一样的字符)。要把A转换成B,所需最少操作数是多少?strlen <= 100
input:
zzzzzfzzzzz
abcdefedcba
output:
6
两部分dp:1.从空白串转换到B。2.从A转换到B
- 从空白串转换到B:
①B[i] == B[j],原区间[i,j]的最少操作次数 等于 分别去掉两个端点的区间的最少操作次数,例如B = abbba,最少刷两遍,第一遍刷aaaaa,第二遍刷bbb;去掉头变为bbba,也要刷两遍;去掉尾变abbb,也要刷两遍。
②B[i] != B[j],用标准的区间操作,把区间分为[i,k]和[k+1,j]两部分来dp
- 从A转换到B
①A[j] == B[j],不用转换,有dp[1][j] = dp[1][j-1]
②A[j] != B[j],用标准的区间操作,把区间分为[i,k]和[k+1,j]两部分来dp;这里利用了从空白串转化为B的结果,当区间[k+1,j]内的A和B字符不同时,从A转化到B与从空白串转化到B是一样的
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int dp[102][102];
string a,b;
void solve(const int& n){
for(int len=2;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j = i+len-1;
dp[i][j] = inf;
if(b[i] == b[j])
dp[i][j] = dp[i+1][j];
else for(int k = i;k < j;k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j]);
}
}
for(int j=1;j<=n;j++){
if(a[j] == b[j])
dp[1][j] = dp[1][j-1];
else for(int k=1;k<j;k++)
dp[1][j] = min(dp[1][j], dp[1][k] + dp[k+1][j]);
}
cout << dp[1][n] << '\n';
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
while(cin >> a && cin >> b){
int n = a.size();
a = ' ' + a;
b = ' ' + b;
for(int i=1;i<=n;i++) dp[i][i] = 1;
solve(n);
}
return 0;
}
洛谷P2858

上周的神孙权那道题和这个很像,都是每次从头、尾中的一个开始摸。这道题为为每一个摸的数字加了权
区间dp状态转移方程:
dp[i][j] = max(dp[i+1][j] + a[i]*(n-len+1), dp[i][l-1] + a[j]*(n-len+1))
也就是从小区间的最优解递推到大区间
有个坑:初始化每个长度为1的区间的值为a[i]*n,因为dp是从长度为2的区间开始状态转移的,而长度为2的区间由长度为1的区间转移过来,长度为1就表示这个数是最后一天摸的,因此初始化要*n
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e3+5;
int dp[maxn][maxn],a[maxn];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) dp[i][i] = a[i]*n;
for(int len = 2;len<=n;len++){
for(int i = 1;i<=n-len+1;i++){
int j = i+len-1;
dp[i][j] = max(dp[i][j-1] + a[j]*(n-len+1), dp[i+1][j] + a[i]*(n-len+1));
}
}
cout << dp[1][n];
return 0;
}
加分二叉树

又是树,又是dp,咋一看是树形dp?
其实不然,题目的这句话“设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n)”,就表面这道题用区间dp来做
定义状态dp[i][j]是以区间[i,j]为树的最大分数
状态转移方程为
dp[i][j] = max(dp[i][j], dp[i][k-1]*dp[k+1][j] + w[k])
不过这里不能直接死板地转移状态,因为题目要求前序遍历输出树
所以要用到一个dp常用策略:存储每一个时刻的最优决策
这里用root[i][j]数组来存储区间[i,j]的最大值对应的根节点
然后dfs前序遍历输出树
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 35;
int dp[maxn][maxn], w[maxn], root[maxn][maxn];
void dfs(int l,int r){
if(l > r) return;
int mid = root[l][r];
cout << mid << '\n';
dfs(l,mid-1);
dfs(mid+1,r);
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin >> n;
for(int i=1;i<=n;i++) cin >> w[i];
for(int i=1;i<=n;i++){
dp[i][i] = w[i];
dp[i][i-1] = 1;
root[i][i] = i;
}
for(int len = 2;len <= n;len++){
for(int i=1;i+len-1 <=n;i++){
int j = i+len-1;
for(int k = i;k<=j;k++){
if(dp[i][j] < dp[i][k-1]*dp[k+1][j] + w[k]){
dp[i][j] = dp[i][k-1] * dp[k+1][j] + w[k];
root[i][j] = k;
}
}
}
}
cout << dp[1][n] << '\n';
dfs(1,n);
return 0;
}
线性丢番图方程
方程$ ax + by = c \(称为二元线性丢番图方程,其中\) a、b、c \(为已知数,\) x、y $为未知量,问是否有整数解。
$ ax + by = c $实际上是二维x-y平面内的一条直线,这条直线上如果有整数点,那么方程就有解,且有无穷多个整数点(解);这条直线上没有整数点,那么方程就没有整数解。
$ ax + by = c \(有解的充分必要条件是\) d = \gcd(a, b) \(能整数\) c $
因为:
设$ a = d \cdot a' \(,\) b = d \cdot b' \(,其中\) \gcd(a', b') = 1 $
那么方程等价于
$ d(a'x + b'y) = c $
因此$ c \(必须是\) d $的整数倍才有解
如果$ (x_0, y_0) $是一个特解,那么所有解(通解)可以表示为
$
\begin{cases}
x = x_0 + \frac{b}{d} \cdot n \
y = y_0 - \frac{a}{d} \cdot n
\end{cases}
\quad \text{其中 } n \in \mathbb{Z} $
因为:
$ (x_0, y_0) \(是一个格点(整数解的点),移动到另一个格点\)
(x_0 + \Delta x ,y_0 + \Delta y)
$,
有$ a \Delta x + b \Delta y = 0 \(,最小的整数解为\) \Delta x = b/d \(,\) \Delta y = -a/d $
扩展欧几里得算法 与 二元丢番图方程的解
求解方程$ ax + by = c \(的关键是找到一个特解\) (x_0, y_0) $,
用扩展欧几里得算法求一个特解$ (x_0, y_0) $的代码如下:
//返回 d = gcd(a,b); 并返回 ax + by = c的特解x,y
typedef long long ll;
ll extend_gcd(ll a,ll b,ll &x,ll &y){
if(b == 0){x = 1; y = 0; return a;}
ll d = extend_gcd(b,a%b,y,x);
y -= a/b * x;
return d;
}
该算法很高效,为$ (O_{log\ min(a,b)}) $
例题--洛谷P1516青蛙的约会

可以发现,两只青蛙跳的路程取余L是同余的,也就是方程
$ x+km≡y+kn\ (mod\ L) $
$ k $即为所求。变化方程为
$ x+km−(y+kn)=Lz\ ,\ z∈Z $
再将其变化为
$ k(m-n) - tL=-(x+y) $
令$ W = (n-m) \(,\) S = (x-y) $
方程就变化为
$ kW + tL = S $
这就是一个二元方程了,要做的就是求出最小解$ k_{min} $
用exgcd解,方程就转化成
$ k_jW +t_jL = (W,L) $
求出的$ k_j $就是一个特解
所有解可以表示为
$ k_i=k_j+c\frac{L}{gcd(W,L)}
$
这个方程对于$ c∈Z $而言,想通过特解推出一个最小解,可以这样做
$ k_{min}=k_j\ mod\ \frac{L}{gcd(W,L)} $
而因为这个$ k \(<font style="color:rgb(64, 64, 64);">是建立在 exgcd 得出的方程上的,方程右边是</font>\) gcd(W,L) \(<font style="color:rgb(64, 64, 64);">而不是</font>\) S \(<font style="color:rgb(64, 64, 64);">。所以最后我们需要将结果</font>\) ×\frac{S}{gcd(W,L)} $
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0) {x = 1; y = 0; return a;}
int d = exgcd(b,a%b,y,x);
y -= a/b *x;
return d;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll x,y,m,n,l;
cin >> x >> y >> m >> n >> l;
ll k,t, b = n-m, a = x-y;
if(b < 0){
b = -b;
a = -a;
}
int d = exgcd(abs(m-n),l,k,t);
if(a % d !=0) cout << "Impossible\n";
else cout << (k*a/d % (l/d) + (l/d))%(l/d) << '\n';
return 0;
}
裴蜀定理(贝祖定理)
如果$ a \(与\) b \(均为整数,则有整数\) x \(和\) y \(使得\) ax+by=gcd(a,b) $
推论
整数$ a \(与\) b \(互素当且仅当存在整数\) x \(和\) y \(,使得\) ax+by=1 $
一道例题--洛谷P4549

n = 1时,答案就是输入的数
n > 1时,先看前两个数$ A_1X_1+A_2X_2=C \(,由于\) d = gcd(A_1,A_2)\ | \ C \(,显然这里\) S \(取\) gcd(A_1,A_2) $时最小;后面的数和前面的结果进行gcd即可
注意,当$ A_i<0 $时,gcd可能会返回负数,因此最后一步输出正数即可
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,num1;
cin >> n >> num1;
for(int i=2;i<=n;i++){
int num2;
cin >> num2;
num1 = __gcd(num1,num2);
}
cout << (num1>0 ? num1:-num1) << '\n';
return 0;
}
HUD5512 ICPC亚洲区沈阳站2015

问题可转化为线性方程组$ ax + by = c $的解的个数减去2
设解的个数为$ P \(,显然,\) P $为奇数时Yuwgna胜,为偶数时Iaka胜
运用裴祖定理,
由于$ gcd(a,b) \ | \ c \(,因此解的个数就是\) gcd(a,b) \(的倍数个,\) P = n / gcd(a,b) $
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t,cnt = 1;
cin >> t;
while(t--){
int n,a,b;
cin >> n >> a >> b;
int p = n/__gcd(a,b)-2;
cout << "Case #" << cnt++ << ": "<< (p&1 ? "Yuwgna\n" : "Iaka\n");
}
return 0;
}
pick定理
在一个平面直角坐标系内,如果一个多边形的顶点都是格点,多边形的面积等于边界上格点数的一半加上内部的格点数减1,即$ s = j/2 + k - 1 $
同余
$ a≡b\ (mod\ m) \(称为\) a \(和\) b \(模\) m $同余
这里有$ m\ |\ (a-b) $
同余式转等式
若$ a \(和\) b \(是整数,\) m \(是正整数,则\) a≡b\ (mod\ m) \(当且仅当存在整数\) k \(,使得\) a=b+km $。
例如$ 19≡-2(mod\ 7) \(,有\) 19 = -2+3×7 $
模运算性质
(a + b) %p = (a%p + b%p) %p
(a * b) %p = (a%p * b%p) %p
这是加法和乘法的模运算。对于除法,也能满足
(a / b) %p = (a%p / b%p) %p吗?
不满足!举反例a=8,b=2,p=6就不满足
逆Inverse
求解一般形式的同余方程$ ax≡b\ (mod\ m) $,需要用到逆(Inverse)
给定整数$ a \(,且满足\) gcd(a,m) = 1 \(,称\) ax≡1(mod\ m) \(的一个解为\) a \(模\) m \(的逆,记为\) a^{-1} $。
例如,$ 8x≡1\ (mod\ 31) $,丢番图方程:
$ 8x+31y=1 $
有一个解是$ x = 4 $,4是8模31的逆,因为4×8-1才能整除31。所有解,如35,66等,都是8模31的逆
扩展欧几里得算法 求单个逆
例题洛谷P1082,求解同余方程$ ax≡1\ (mod\ m) $



$ ax≡1\ (mod\ m) \(,即丢番图方程\) ax +my=1 \(,用扩展欧几里得算法求出一个特解\) x_0 \(,通解就是\) x = x_0 + tm \(。再用\) ((x_0\ mod \ m)+m)\ mod\ m $求出最小正整数解
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
//先用扩展欧几里得方法求出特解
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0) {x = 1; y = 0; return a;}
ll d = exgcd(b,a%b,y,x);
y -= a/b * x;
return d;
}
ll inv(ll a,ll m){
ll x,y;
exgcd(a,m,x,y);
return (x%m + m) % m; //因为要最小正整数
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll a,b;
cin >> a >> b;
cout << inv(a,b);
return 0;
}
百度之星2024题集
BD202401

第一眼以为是背包,题读完了发现贪个心完事
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+5;
using pii = pair<int,int>;
pii a[maxn];
bool cpa(pii a,pii b){
return (a.first+a.second) < (b.first+b.second);
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,b;
cin >> n >> b;
for(int i=1;i<=n;i++){
int p,s;
cin >> p >> s;
a[i] = {p,s};
}
sort(a+1,a+n+1,cpa);
for(int i=1;i<=n;i++){
if(b - a[i].first - a[i].second >= 0)
b -= a[i].first+a[i].second;
else if(b - a[i].first/2 - a[i].second >= 0){
cout << i << '\n';
break;
}
else{
cout << i-1 << '\n';
break;
}
}
return 0;
}
A

签到。自己写几组数据可以发现,直接输出最大的数就行。
贪心地想,自己&自己是最优解,在每个数中,最大数按位与出来的结果是最大的
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin >> t;
while(t--){
int n,ans = 0;
cin >> n;
for(int i = 1;i<=n;i++){
int cache;
cin >> cache;
ans = max(ans, cache);
}
cout << ans << '\n';
}
return 0;
}
B

打表--查表
#include <bits/stdc++.h>
using namespace std;
map <char,string> ma;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ma['0'] = "---";
ma['1'] = "--x";
ma['2'] = "-w-";
ma['3'] = "-wx";
ma['4'] = "r--";
ma['5'] = "r-x";
ma['6'] = "rw-";
ma['7'] = "rwx";
int t;
cin >> t;
while(t--){
string s;
cin >> s;
for(char i:s) cout << ma[i];
cout << '\n';
}
return 0;
}
[墓碑]题目已丢失
emmm队友发来一道数据结构,随手做了做,题目没保存下来......
立个墓碑
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
map <int,int> ma;
int sec[maxn]{};
inline void inser(int num) {ma[num]++;}
inline void print() {cout << ma.begin()->first << '\n';}
inline void era() {ma.erase(ma.begin()->first);}
inline void era(int num) {ma[num]--;if(ma[num] == 0) ma.erase(num);}
inline void xiu(int n1,int n2) {ma[n1]--;ma[n2]++;if(ma[n1] == 0) ma.erase(n1);}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n, cnt = 0;
cin >> n;
for(int i=1;i<=n;i++){
string s;
cin >> s;
if(s[0] == 'I'){
int num;
cin >> num;
inser(num);
sec[++cnt] = num;
}
else if(s[0] == 'P') print();
else if(s[0] == 'D' && s.size() == 2) era();
else if(s[0] == 'D'){
int k;
cin >> k;
era(sec[k]);
}
else{
int k,x;
cin >> k >> x;
xiu(sec[k],x);
}
}
return 0;
}

浙公网安备 33010602011771号