快速渡河问题

​https://vjudge.net/problem/POJ-1700​

此问题可能有两种解,求这两个解的min即可
第一种是前两个出发,1返回,最后两名出发,2返回
第二种是1和最后一个出发,1返回,1又和当前最后一个出发,1返回。。。。(即每次由1带领)

#include <cstdio>
#include <algorithm>

using namespace std;

int a[1005];

void fn(int n){
int left = n;
int ans = 0;
while (left > 0){
if(left==1){
ans += a[0];
break;
}else if(left==2){
ans += a[1];
break;
}else if(left==3){
ans += a[2] + a[0] + a[1];
break;
}else{
// 1,2出发,1返回,最后两名出发,2返回
int s1 = a[1] + a[0] + a[left-1] + a[1];
// 1,3出发,1返回,1,4出发,1返回,1,2过河
int s2 = a[left-1] + a[left-2] + 2 * a[0];
ans += min(s1, s2);
left -= 2;
}
}

printf("%d\n", ans);
}

int main(){

int testnum, n;
scanf("%d", &testnum);
for (int i = 0; i < testnum; ++i) {
scanf("%d", &n);
for (int j = 0; j < n; ++j) {
scanf("%d", &a[j]);
}
fn(n);
}
return 0;
}

区间问题

区间调度问题

有n项工作,每项工作分别在si时间开始,在ti时间结束.
对于每项工作,你都可以选择参与与否.如果选择了参与,那么自始至终都必须全程参与.
此外,参与工作的时间段不能重复(即使是开始的瞬间和结束的瞬间的重叠也是不允许的).
你的目标是参与尽可能多的工作,那么最多能参与多少项工作呢?
1<=n<=100000
1<=si<=ti<=10^9
输入:
第一行:n
第二行:n个整数空格隔开,代表n个工作的开始时间
第三行:n个整数空格隔开,代表n个工作的结束时间

#include <cstdio>
#include <algorithm>

using namespace std;

struct Job{
int s;
int t;
Job(){

}
Job(int s, int t){
this->s = s;
this->t = t;
}
};

int n;
Job js[100005];

bool cmp(Job j1, Job j2){
if(j1.t==j2.t){
return j1.s > j2.s;
}
return j1.t < j2.t;
}

int f(int n){
int cnt = 1;
int y = js[0].t;
for (int i = 0; i < n; ++i) {
if(js[i].s > y){
cnt++;
y = js[i].t;
}
}
return cnt;
}

int main(){

// 5
// 1 2 4 6 8
// 3 5 7 9 10

scanf("%d", &n);

Job cur;
for (int i = 0; i < n; ++i) {
scanf("%d", &(js[i].s));
}

for (int i = 0; i < n; ++i) {
scanf("%d", &(js[i].t));
}

sort(js, js + n, cmp);

printf("%d", f(n));

return 0;
}

区间选点问题

​https://vjudge.net/problem/POJ-1201​

​只有用树状数组时,才能AC​

#include <cstdio>
#include <algorithm>

using namespace std;

struct Node{
int s;
int t;
int c;

Node(){}
Node(int s, int t, int c){
this->s = s;
this->t = t;
this->c = c;
}
};

bool cmp(Node n1, Node n2){
if(n1.t==n2.t){
n1.s < n2.s;
}
return n1.t < n2.t;
}

// 求s~t区间标记了多少个点
int sum(int axis[], int s, int t){
int sum = 0;
for (int i = s; i <= t; ++i) {
sum += axis[i];
}
return sum;
}

int main(){

int n;

scanf("%d", &n);
Node ns[n];

for (int i = 0; i < n; ++i) {
scanf("%d%d%d", &(ns[i].s),&(ns[i].t),&(ns[i].c));
}

// 按区间右端点排序
sort(ns, ns+n, cmp);

int max = ns[n-1].t; // 右端最大值

int axis[max+1]; // 标记轴上的点是否被选中

for (int i = 0; i < n; ++i) {
// 查阅区间中有多少个点
int s = ns[i].s;
int t = ns[i].t;
int cnt = sum(axis, s, t);
// 如果不够,从区间右端开始标记,遇到标记过的就跳过
ns[i].c -= cnt; // 需要新增的点的数量
while (ns[i].c>0){
if(axis[t]==0){ // 从区间中开始选点
axis[t] = 1;
ns[i].c--;
t--;
}else{
t--;
}
}
}

printf("%d\n", sum(axis, 0, max));

return 0;
}

区间覆盖问题

#include<cstdio>
#include <algorithm>

using namespace std;

struct Job{
int s;
int t;
};

bool cmp(Job j1, Job j2){
if(j1.s==j2.s){
return j1.t < j2.t;
}
return j1.s < j2.s;
}

int n,T;

Job js[25005];

int main(){

scanf("%d%d",&n,&T);

for (int i = 0; i < n; ++i) {
scanf("%d%d", &(js[i].s), &(js[i].t));
}

sort(js, js + n, cmp);

int start = 1; // 每次要覆盖的区间的起点
int end = 1;
int ans = 1;

for (int i = 0; i < n; ++i) {
int s = js[i].s; // 当前线段的起点
int t = js[i].t;

if(i==0&&s>1){
break;
}

if(s <= start){
end = max(t, end); // 更新更右端的端点
}else{ // 开始下一个区间
ans++; // 上一个覆盖目标已经达成
start = end + 1; // 更新起点
if(s <= start){
end = max(t, end);
} else{
break;
}
}
if(end >= T){
break;
}
}

if(end < T){
printf("-1\n");
}else{
printf("%d", ans);
}
return 0;
}

字典序最小问题

​https://vjudge.net/problem/POJ-3617​

#include <cstdio>
#include <algorithm>
#include <string>

using namespace std;

int n;

void f(string s2){
string s1 = s2;
reverse(s2.begin(), s2.end());
string rs = "";
int cnt = 0;
while (rs.length() < n){
if(s1 <= s2){
rs.push_back(s1[0]);
s1.erase(s1.begin());
}else{
rs.push_back(s2[0]);
s2.erase(s2.begin());
}

if(rs.length()%80==0){
printf("%s\n", rs.substr(cnt*80).c_str());
cnt++;
}
}
if(rs.length()>cnt*80){
printf("%s\n", rs.substr(cnt*80).c_str());
}
}

int main(){

scanf("%d", &n);
getchar();
string s = "";
char c;
for (int i = 0; i < n; ++i) {
scanf("%c", &c);
getchar();
s.push_back(c);
}

f(s);

return 0;
}

贪心/动归问题_递增子序列

#include <iostream>

using namespace std;

/*
Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,...,单位为美元)。钢条的长度均为整英寸。
| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| - | - | - | - | - | - | - | - | - | - |
价格pi | 1 | 5 | 8 | 16 | 10 | 17 | 17 | 20 | 24 | 30 |
钢条切割问题是这样的:给定一段长度为n英 寸的钢条和一个价格表pi(i=1,.,n), 求切割钢条方案,使得销售收益rn最大。
注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。
*/

const int n = 10;
int p[] = {1, 5, 8, 16, 10, 17, 17, 20, 24, 30};
int vs[n + 1];

int dp(){
vs[0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
// val[取当前长度] + dp[total - 当前长度], dp[total]
vs[i] = max(p[j - 1] + vs[i - j], vs[i]);
}
}
return vs[n];
}

int main(){

cout << dp();

return 0;
}

LCS(最长公共子序列)问题

#include <iostream>
#include <string>

using namespace std;

string solution(string s1, string s2){

int len1 = s1.length();
int len2 = s2.length();

int dp[len1 + 1][len2 + 1];
int flag = 0;
// 初始化第一列
for (int i = 1; i <= len1; ++i) {
if(flag == 1){
dp[i][1] = 1;
}else if(s1[i-1] == s2[0]){
dp[i][1] = 1;
flag = 1;
}else{
dp[i][1] = 0;
}
}
flag = 0;
// 初始化第一行
for (int i = 1; i <= len2; ++i) {
if(flag == 1){
dp[1][i] = 1;
}else if(s2[i-1] == s1[0]){
dp[1][i] = 1;
flag = 1;
}else{
dp[1][i] = 0;
}
}

for (int i = 2; i <= len1; ++i) {
for (int j = 2; j <= len2; ++j) {
int m = max(dp[i-1][j], dp[i][j-1]);
if(s1[i-1] == s2[j-1]){
dp[i][j] = max(m, dp[i-1][j-1] + 1);
}else{
dp[i][j] = m;
}
}
}

int m = len1;
int n = len2;

string s = "";

while (m > 0 && n > 0){
// 比左和上大,一定是当前位置字符相同
if(dp[m][n] > max(dp[m-1][n], dp[m][n-1])){
s.insert(s.begin(), s1[m-1]);
m--;
n--;
}else{
// 一定选择的是左边和上边的大者
if(dp[m-1][n] > dp[m][n-1]){
m--; // 往上移
}else{
n--; // 往左移
}
}
}

return s;
}


int main(){


return 0;
}

完全背包问题

0-1背包 ==> 物品的选取数量不限

dp[i][j] = max(dp[ i - 1 ][ j ], dp[ i ][ i - w[ i ] ] + v[ i ])

注意:对于完全背包问题,取当前物品时是从当前行求得最大值,因为在同一行中,i - w[ i ] 列时,已经包含了取1,2…x-1个当前物品的情况

最长递增子序列

​第一种dp,此时dp记录以当前元素结尾的最长递增子序列​

#include <iostream>

using namespace std;

const int n = 10;

int arr[] = {4, 3, 1, 5, 7, 4, 8, 9, 2};

// 此时dp记录以当前元素结尾的最长递增子序列
int dp[n];

int fn(){
dp[0] = 1;
for (int i = 1; i < n; ++i) {
int cnt = 1;
for (int j = i - 1; j >= 0; ++j) {
// 在前面找到比当前元素小的元素
// 此时当前元素可以作为结尾
if(arr[i] > arr[j]){
cnt = max(cnt, dp[j] + 1);
}
}
dp[i] = cnt;
}
int ans = -1;
for (int i = 0; i < n; ++i) {
ans = max(ans, dp[i]);
}
return ans;
}

int main(){
cout << fn();
}

​第2中dp,此时dp更新规则为,如果当前元素大于dp中现在的最后一个元素,则dp[last++] = cur, 否则,找到第一个大于当前元素的位置进行替换,最后索引last即答案。(last代表递增子序列的长度)​

#include <iostream>
#include <algorithm>

using namespace std;

const int n = 10;

int arr[] = {4, 3, 1, 5, 7, 4, 8, 9, 2};

int dp[n + 1];

int fn(){

dp[1] = 0; // 长度为1的最长递增子序列,初始化为第一个元素。

int p = 1;
for (int i = 1; i < n; ++i) {
if(arr[i] > dp[p]){
dp[++p] = arr[i];
}else{
// 在dp数组中找到第一个大于arr[i]的数字进行替换
int ind = upper_bound(dp + 1, dp + p + 1, arr[i]) - dp;
dp[ind] = arr[i];
}
}

return p;
}

int main(){
cout << fn();
}