快速渡河问题
https://vjudge.net/problem/POJ-1700
此问题可能有两种解,求这两个解的min即可
第一种是前两个出发,1返回,最后两名出发,2返回
第二种是1和最后一个出发,1返回,1又和当前最后一个出发,1返回。。。。(即每次由1带领)
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个工作的结束时间
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
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;
}
区间覆盖问题
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
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;
}
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(最长公共子序列)问题
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记录以当前元素结尾的最长递增子序列
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代表递增子序列的长度)
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();
}