最大子段和
constint maxn = 100010;
int arr[maxn], dp[maxn];
int t, n, a, res;
int main()
{
cin >> t;
int num = t;
int Case = 0;
while (t--)
{
int Max, start, lst, fst, n;
lst = fst = start = 1;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> dp[i];
Max = dp[1];
for (int i = 2; i <= n; i++)
{
if (dp[i - 1] >= 0)
{
dp[i] = dp[i - 1] + dp[i];
}
else
start = i;
if (dp[i] > Max)
{
fst = start;//更新起始位置
lst = i;
Max = dp[i];
}
}
cout << "Case " << ++Case << ":\n";
cout << Max << " " << fst << " " << lst << ((Case == num) ? "\n" : "\n\n");
}
}
最长有序子序列
/*
* 递增(默认)
* 递减
* 非递增
* 非递减 (1)>= && < (2)< (3)>=
*/constint MAXN = 1001;
int a[MAXN], f[MAXN], d[MAXN]; // d[i] 用于记录 a[0...i] 以 a[i] 结尾的最大长度int bsearch(constint *f, int size, constint &a)
{
int l = 0, r = size - 1;
while (l <= r)
{
int mid = (l + r) / 2;
if (a > f[mid - 1] && a <= f[mid]) // (1)
{
return mid;
}
elseif (a < f[mid]){
r = mid - 1;
}
else{
l = mid + 1;
}
}
return -1;
}
int LIS(constint *a, constint &n)
{
int i, j, size = 1;
f[0] = a[0];
d[0] = 1;
for (i = 1; i < n; ++i)
{
if (a[i] <= f[0]) // (2)
{
j = 0;
}
elseif (a[i] > f[size - 1]) // (3)
{
j = size++;
}
else
{
j = bsearch(f, size, a[i]);
}
f[j] = a[i];
d[i] = j + 1;
}
return size;
}
int main()
{
int i, n;
while (scanf("%d", &n) != EOF)
{
for (i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
}
printf("%d\n", LIS(a, n)); // 求最大递增 / 上升子序列(如果为最大非降子序列,只需把上面的注释部分给与替换)
}
return0;
}
0-1背包问题
constint maxn = 100;
int vis[maxn], d[maxn],f[maxn][maxn];
int v[maxn], w[maxn];
int n, c;
int main()
{
int v, w;
cin >> n>>c;
for (int i = 1; i <= n; i++)
{
cin >> v >> w;
for (int j = 0; j <= c; j++)
{
f[i][j] = (i == 1 ? 0 : f[i - 1][j]);
if (j >= v)
f[i][j] = max(f[i][j], f[i - 1][j-v] + w);
}
}
cout << f[n][c] << endl;
system("pause");
}
constint maxn = 100;
int v[maxn], w[maxn];
int d[maxn][maxn];
int main()
{
int n, c;
cin >> n >> c;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = n; i >= 1; i--)
{
for (int j = 0; j <= c; j++)
{
d[i][j] = (i == n ? 0 : d[i+1][j]);
if (j >= v[i])
{
d[i][j] = max(d[i][j], d[i + 1][j - v[i]] + w[i]);
}
}
}
cout << d[1][c] << endl;
system("pause");
}
constint maxn = 100;
int f[maxn];
int n, c;
int main()
{
int v, w;
memset(f,0,sizeof(f));
cin >> n>>c;
for (int i = 1; i <= n; i++)
{
cin >> v >> w;
for(int j=c;j>=0;j--)
{
if(j>=v)
f[j]=max(f[j],f[j-v]+w);
}
}
cout << f[c] << endl;
system("pause");
}
多重背包
constint maxn = 110;
int n, m;
int dp[maxn], p[maxn], h[maxn], c[maxn];
void CompleteBack(int v, int w)
{
for (int i=v; i <= n; i++)
{
dp[i] = max(dp[i], dp[i - v] + w);
}
}
void OnePack(int v, int w)
{
for (int i = n; i >= v; i--)
{
dp[i] = max(dp[i], dp[i - v] + w);
}
}
int main()
{
int C, k = 1, count = 0;
cin >> C;
while (C--)
{
cin >> n >> m;//经费 种类memset(dp, 0, sizeof(dp));
for (int i = 1; i <= m; i++)
{
cin >> p[i] >> h[i] >> c[i];//价格 重量 数量if (p[i] * c[i] >= n)
CompleteBack(p[i], h[i]);//完全背包else
{
for (int j = 1; j <=c[i]; (j<< 1))
{
OnePack(j*p[i], j*h[i]);//0-1背包
c[i] = c[i] - j;
}
OnePack(p[i] * c[i], h[i] * c[i]);
}
}
cout << dp[n] << endl;
}
}
物品无限背包问题
constint maxn = 100;
int vis[maxn], d[maxn];
int v[maxn], w[maxn];
int n, c;
int dp(int c)
{
int &ans = d[c];
if (ans != -1)
return ans;
ans = -(1 << 20);
for (int i = 0; i < n; i++)
{
if (c >= v[i])
{
ans = max(ans, dp(c - v[i]) + w[i]);
}
}
return ans;
}
int main()
{
cin >> n>>c;
for (int i = 0; i < n; i++)
cin >> v[i] >> w[i];
memset(d, -1, sizeof(d));
d[0] = 0;
cout<<dp(c);
system("pause");
}
选择m个子序列且不相交的最大和
int T, n, m, x;
int z[1000005];
LL dp[1000005];
LL dp2[1000005];
LL ans;
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &z[i]);
}
memset(dp, 0, sizeof(dp));
memset(dp2, 0, sizeof(dp2));
dp[0] = dp2[0] = 0;
for (int i = 1; i <= m; ++i)
{
ans = -0x7f7f7f7f;
for (int j = i; j <= n; ++j)
{
dp[j] = max(dp[j - 1], dp2[j - 1]) + z[j];
dp2[j - 1] = ans;
ans = max(dp[j], ans);
}
}
printf("%lld\n", ans);
}
}
使序列有序交换最少次数
如果只是交换相邻两数,那么最少交换次数为该序列的逆序数。
交换任意两数
/*
* 交换任意两数的本质是改变了元素位置,
* 故建立元素与其目标状态应放置位置的映射关系
*/int getMinSwaps(vector<int> &A)
{
// 排序vector<int> B(A);
sort(B.begin(), B.end());
map<int, int> m;
int len = (int)A.size();
for (int i = 0; i < len; i++)
{
m[B[i]] = i; // 建立每个元素与其应放位置的映射关系
}
int loops = 0; // 循环节个数vector<bool> flag(len, false);
// 找出循环节的个数for (int i = 0; i < len; i++)
{
if (!flag[i])
{
int j = i;
while (!flag[j])
{
flag[j] = true;
j = m[A[j]]; // 原序列中j位置的元素在有序序列中的位置
}
loops++;
}
}
return len - loops;
}
vector<int> nums;
int main()
{
nums.push_back(1);
nums.push_back(2);
nums.push_back(4);
nums.push_back(3);
nums.push_back(5);
int res = getMinSwaps(nums);
cout << res << '\n';
return0;
}
交换任意区间
/*
* 默认目标映射关系是 key 1 => val 1 …… key n => val n
* 如果序列不是 1~n 可以通过 map 建立新的目标映射关系
* 交换任意区间的本质是改变了元素的后继,故建立元素与其初始状态后继的映射关系
*/constint MAXN = 30;
int n;
int vis[MAXN];
int A[MAXN], B[MAXN];
int getMinSwaps()
{
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
{
B[A[i]] = A[i % n + 1];
}
for (int i = 1; i <= n; i++)
{
B[i] = (B[i] - 2 + n) % n + 1;
}
int cnt = n;
for (int i = 1; i <= n; i++)
{
if (vis[i])
{
continue;
}
vis[i] = 1;
cnt--;
for (int j = B[i]; j != i; j = B[j])
{
vis[j] = 1;
}
}
return cnt;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> A[i];
}
int res = getMinSwaps();
cout << res << '\n';
return0;
}
给出n个数和Q个询问(l,r),对于每个询问求出(l,r)之间连续出现次数最多的次数。
/*
* 求区间中数出现的最大频率
* 方法一:线段树.
* 先离散化。因为序列是升序,所以先将所有值相同的点缩成一点。这样n规模就缩小了。建立一个数据结构
* 记录缩点的属性:在原序列中的值id,和该值有多少个num比如序列
* 10
* -1 -111113101010
* 缩点后为:下标 1234
* id -11310
* num 2413
* 然后建树,树的属性有区间最大值(也就是频率)和区间总和。
* 接受询问的时候。接受的是原来序列的区间[be,ed]我们先搜索一下两个区间分别在离散化区间后的下标。
* 比如接受[2,3]时候相应下标区间就是[1,2];[3,10]的相应下标区间是[2,4];
* 处理频率的时候,我们发现两个极端,也就是左右两个端点的频率不好处理。因为它们是不完全的频率
* 也就是说有部分不在区间内。但是如果对于完全区间,也就是说左右端点下标值完全在所求区间内。
* 比如上例的[2,3]不好处理。但是如果是[1,6],或是[1,10]就很好处理了,只要像RMQ一样询问区间最大值就可以了。
* 方法二:RMQ.
* 我们可以转化一下问题。将左右端点分开来考虑。
* 现在对于离散后的询问区间我们可以分成3个部分.左端点,中间完全区间,右端点。
* 对于中间完全区间线段树或RMQ都能轻松搞定。只要特判一左右的比较一下就得最后解了。
*/int build(int a, int b);
int query(intindex, int a, int b);
const int N = 100010;
struct NODE
{
int b, e; // 区间[b, e]
int l, r; // 左右子节点下标
int number; // 区间内的最大频率值
intlast; // 以 data[e]结尾且与 data[e]相同的个数:data[e-last+1]...data[e]
} node[N * 2 + 1];
int len, data[N];
int main()
{
int n;
while (scanf("%d", &n), n)
{
int i, q, a, b;
scanf("%d", &q);
for (i = 0; i < n; i++)
{
scanf("%d", &data[i]);
}
len = 0; // 下标
build(0, n - 1);
while (q--)
{
scanf("%d%d", &a, &b);
printf("%d\n", query(0, a - 1, b - 1)); // 输出区间的最大频率值,而非data[]
}
}
return0;
}
int build(int a, int b) // 建立线段树
{
int temp = len, mid = (a + b) / 2;
node[temp].b = a, node[temp].e = b;
len++;
if (a == b)
{
node[temp].number = 1;
node[temp].last = 1;
return temp;
}
node[temp].l = build(a, mid);
node[temp].r = build(mid + 1, b);
int left_c = node[temp].l, right_c = node[temp].r, p, lcount = 0, rcount = 0, rec, max = 0;
rec = data[mid];
p = mid;
while (p >= a && data[p] == rec)
{
p--, lcount++;
}
node[left_c].last = lcount;
rec = data[mid + 1];
p = mid + 1;
while (p <= b && data[p] == rec)
{
p++, rcount++;
}
node[right_c].last = rcount;
if (data[mid] == data[mid + 1])
{
max = lcount + rcount;
}
if (node[left_c].number > max)
{
max = node[left_c].number;
}
if (node[right_c].number > max)
{
max = node[right_c].number;
}
node[temp].number = max;
return temp;
}
int query(intindex, int a, int b)
{
int begin = node[index].b;
int end = node[index].e;
int mid = (begin + end) / 2;
if (a == begin && b == end)
{
return node[index].number;
}
if (a > mid)
{
return query(node[index].r, a, b);
}
if (b < mid + 1)
{
return query(node[index].l, a, b);
}
int temp1, temp2, max;
if (node[index].l > 0)
{
temp1 = query(node[index].l, a, mid);
}
if (node[index].r > 0)
{
temp2 = query(node[index].r, mid + 1, b);
}
max = temp1 > temp2 ? temp1 : temp2;
if (data[mid] != data[mid + 1])
{
return max;
}
temp1 = node[node[index].l].last > (mid - a + 1) ? (mid - a + 1) : node[node[index].l].last;
temp2 = node[node[index].r].last > (b - mid) ? (b - mid) : node[node[index].r].last;
if (max < temp1 + temp2)
{
max = temp1 + temp2;
}
return max;
}
机器工作调度
2台机器,n件任务,必须先在S1上做,再在S2上做.
任务之间先做后做任意.求最早的完工时间.
这是一个经典问题: 2台机器的情况下有多项式算法(Johnson算法),3台或以上的机器是NP-hard算法。
Johnson算法:
(1)把作业按工序加工时间分成两个子集,第一个集合中在S1上做的时间比在S2上少,其它的作业放到第二个集合;
先完成第一个集合里面的作业,再完成第二个集合里的作业.
(2)对于第一个集合,其中的作业顺序是按在S1上的时间的不减排列;
对于第二个集合, 其中的作业顺序是按在S2上的时间的不增排列.
constint MAXN = 5e4 + 5;
structtask
{
int a;
int b;
} TaskA[MAXN], TaskB[MAXN];
bool cmpA(task a, task b)
{
return a.a <= b.a;
}
bool cmpB(task a, task b)
{
return a.b >= b.b;
}
int main(int argc, constchar * argv[])
{
int N;
cin >> N;
int a, b;
int posA = 0, posB = 0;
int sumA = 0, sumB = 0;
for (int i = 0; i < N; i++)
{
scanf("%d %d", &a, &b);
if (a < b)
{
TaskA[posA].a = a;
TaskA[posA++].b = b;
sumA += b;
}
else
{
TaskB[posB].a = a;
TaskB[posB++].b = b;
sumB += a;
}
}
sort(TaskA, TaskA + posA, cmpA);
sort(TaskB, TaskB + posB, cmpB);
for (int i = 0; i < posB; i++)
{
TaskA[posA++] = TaskB[i];
}
int ans = TaskA[0].a + TaskA[0].b;
int sum = TaskA[0].a;
for (int i = 1; i < posA; i++)
{
sum += TaskA[i].a;
ans = sum < ans ? ans + TaskA[i].b : sum + TaskA[i].b;
}
cout << ans << '\n';
return0;
}
带权值的并查集
constint N = 1010;
struct lset
{
int p[N], rank[N], sz;
void link(int x, int y)
{
if (x == y)
{
return ;
}
if (rank[x] > rank[y])
{
p[y] = x;
}
else
{
p[x] = y;
}
if (rank[x] == rank[y])
{
rank[y]++;
}
return ;
}
void makeset(int n)
{
sz = n;
for (int i = 0; i < sz; i++)
{
p[i] = i;
rank[i] = 0;
}
return ;
}
int findset(int x)
{
if (x != p[x])
{
p[x] = findset(p[x]);
}
return p[x];
}
void unin(int x, int y)
{
link(findset(x), findset(y));
return ;
}
void compress()
{
for (int i = 0; i < sz; i++)
{
findset(i);
}
return ;
}
};
拓扑排序
/*
* 拓扑排序
* INIT:edge[][]置为图的邻接矩阵;cnt[0...i...n-1]:顶点i的入度。
*/constint MAXV = 1010;
int edge[MAXV][MAXV];
int cnt[MAXV];
void TopoOrder(int n)
{
int i, top = -1;
for (i = 0; i < n; ++i)
{
if (cnt[i] == 0)
{ // 下标模拟堆栈
cnt[i] = top;
top = i;
}
}
for (i = 0; i < n; i++)
{
if (top == -1)
{
printf("存在回路\n");
return ;
}
else
{
int j = top;
top = cnt[top];
printf("%d", j);
for (int k = 0; k < n; k++)
{
if (edge[j][k] && (--cnt[k]) == 0)
{
cnt[k] = top;
top = k;
}
}
}
}
}
星期问题
/*
* 已知1752年9月3日是Sunday,并且日期控制在1700年2月28日后
*/char name[][15] = { "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"};
int main()
{
int d, m, y, a;
printf("Day: ");
scanf("%d", &d);
printf("Month: ");
scanf("%d", &m);
printf("Year: ");
scanf("%d", &y);
// 1月2月当作前一年的13,14月if (m == 1 || m == 2)
{
m += 12;
y--;
}
// 判断是否在1752年9月3日之前,实际上合并在一起倒更加省事if ((y < 1752) || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3))
{
// 因为日期控制在1700年2月28日后,所以不用考虑整百年是否是闰年
a = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 + 5) % 7;
}
else
{
// 这里需要考虑整百年是否是闰年的情况
a = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7; // 实际上这个可以当做公式背下来
}
printf("it's a %s\n", name[a]);
return0;
}
多项式求根(牛顿法)
/*
* 牛顿法解多项式的根
* 输入:多项式系数c[],多项式度数n,求在[a,b]间的根
* 输出:根 要求保证[a,b]间有根
*/doublefabs(double x)
{
return (x < 0) ? -x : x;
}
double f(int m, double c[], double x)
{
int i;
double p = c[m];
for (i = m; i > 0; i--)
{
p = p * x + c[i - 1];
}
return p;
}
int newton(double x0, double *r, double c[], double cp[], int n, double a, double b, double eps)
{
int MAX_ITERATION = 1000;
int i = 1;
double x1, x2, fp, eps2 = eps / 10.0;
x1 = x0;
while (i < MAX_ITERATION)
{
x2 = f(n, c, x1);
fp = f(n - 1, cp, x1);
if ((fabs(fp) < 0.000000001) && (fabs(x2) > 1.0))
{
return0;
}
x2 = x1 - x2 / fp;
if (fabs(x1 - x2) < eps2)
{
if (x2 < a || x2 > b)
{
return0;
}
*r = x2;
return1;
}
x1 = x2;
i++;
}
return0;
}
double Polynomial_Root(double c[], int n, double a, double b, double eps)
{
double *cp;
int i;
double root;
cp = (double *)calloc(n, sizeof(double));
for (i = n - 1; i >= 0; i--)
{
cp[i] = (i + 1) * c[i + 1];
}
if (a > b)
{
root = a;
a = b;
b = root;
}
if ((!newton(a, &root, c, cp, n, a, b, eps)) && (!newton(b, &root, c, cp, n, a, b, eps)))
{
newton((a + b) * 0.5, &root, c, cp, n, a, b, eps);
}
free(cp);
if (fabs(root) < eps)
{
returnfabs(root);
}
elsereturn root;
}
1/n循环节长度
/*
* 求1/i的循环节长度的最大值,i<=n
*/constint MAXN = 1005;
int res[MAXN]; // 循环节长度int main()
{
memset(res, 0, sizeof(res));
int i, temp, j, n;
for (temp = 1; temp <= 1000; temp++)
{
i = temp;
while (i % 2 == 0)
{
i /= 2;
}
while (i % 5 == 0)
{
i /= 5;
}
n = 1;
for (j = 1; j <= i; j++)
{
n *= 10;
n %= i;
if (n == 1)
{
res[temp] = j;
break;
}
}
}
int max_re;
while (cin >> n)
{
max_re = 1;
for (i = 1; i <= n; i++)
{
if (res[i] > res[max_re])
{
max_re = i;
}
}
cout << max_re << endl;
}
return0;
}
A^B约数之和对MOD取模
/*
* 求A^B的约数之和对MOD取模
* 需要素数筛选和合数分解的算法,需要先调用getPrime();
* 参考《合数相关》
* 1+p+p^2+p^3+...+p^n
*/
const int MOD = 1000000;
longlong pow_m(longlong a, longlong n)
{
longlong ret = 1;
longlong tmp = a % MOD;
while(n)
{
if (n & 1)
{
ret = (ret * tmp) % MOD;
}
tmp = tmp * tmp % MOD;
n >>= 1;
}
return ret;
}
// 计算1+p+p^2+...+p^nlonglongsum(longlong p, longlong n)
{
if (p == 0)
{
return0;
}
if (n == 0)
{
return1;
}
if (n & 1)
{
return ((1 + pow_m(p, n / 2 + 1)) % MOD * sum(p, n / 2) % MOD) % MOD;
}
else
{
return ((1 + pow_m(p, n / 2 + 1)) % MOD * sum(p, n / 2 - 1) + pow_m(p, n / 2) % MOD) % MOD;
}
}
// 返回A^B的约数之和%MODlonglong solve(longlong A, longlong B)
{
getFactors(A);
longlong ans = 1;
for (int i = 0; i < fatCnt; i++)
{
ans *= sum(factor[i][0], B * factor[i][1]) % MOD;
ans %= MOD;
}
return ans;
}
约瑟夫环问题的原来描述为,设有编号为0,1,……,n-1的n(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。当任意给定n和m后,设计算法求n个人出圈的次序。
#include <iostream>#include <cstdio>#include <cstring>usingnamespacestd;
int n,m,k;
int main()
{
while (scanf("%d%d",&n,&m))//编号0,1,2,3,.....,n-1
{
int s=0;//只有一个人时最后出圈的人编号为0 for (int i=2;i<=n;i++)
s=(s+m)%i;
printf("%d\n",s);//编号为s的最后出圈
}
return0;
}
打印出圈顺序
#include <iostream>#include <cstdio>#include <cstring>usingnamespacestd;
int n, m, k, p, num, step;
int arr[20];
int main()
{
scanf("%d%d", &n, &m);//人数 步数 for (int i = 1; i <= n; i++)
{
arr[i] = i;
}
p = n;//从位置n开始数m个位置不包括位置n
num = n;//人数num
step = m;//步数stepwhile (num)
{
while (step--)
{
do {
p = (p + n) %n + 1;
} while (arr[p] == 0);//走到下个未出圈的位置
}
printf("%d ", p);
step = m;
num--;
arr[p] = 0;//出圈
}
system("pause");
return0;
}
最长公共子序列const int N = 1010;int a[N][N];int LCS(const char *s1, const char *s2){ // s1:0...m, s2:0...n int m = (int)strlen(s1), n = (int)strlen(s2); int i, j; a[0][0] = 0; for (i =