#include
template<typename item>
class smallest_heap{
private:
item heap[10001];
int len;
public:
smallest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
smallest_heap<item>::smallest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void smallest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]<heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void smallest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]>heap[son+1]) son++;
if(heap[father]>heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item smallest_heap<item>::top(){
return heap[1];
}
template<typename item>
int smallest_heap<item>::size(){
return len;
}
template<typename item>
bool smallest_heap<item>::empty(){
return len;
}
#include
template<typename item>
class largest_heap{
private:
item heap[10001];
int len;
public:
largest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
largest_heap<item>::largest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void largest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]>heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void largest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]<heap[son+1]) son++;
if(heap[father]<heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item largest_heap<item>::top(){
return heap[1];
}
template<typename item>
int largest_heap<item>::size(){
return len;
}
template<typename item>
bool largest_heap<item>::empty(){
return len;
}
同时也可以支持自己编写的类,但须提供“<”或“>”的运算符重载,例如
class T{
private:
int a;
public:
bool operator<(T const &type){
return a<type.a;
}
};
smallest_heap<T> heap;
例题:P1090 合并果子 - 洛谷
时间复杂度:O(n)
#include
#include
#include
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
scanf("%d",&num);
memset(a1,127/3,sizeof(a1));
memset(a2,127/3,sizeof(a2));
for (int i=1;i<=num;i++)
{
scanf("%d",&x);
t[x]++;//桶
}
for (int i=1;i<=20000;i++)
{
while (t[i])//桶排序
{
t[i]--;
a1[++n1]=i;
}
}
int i=1,j=1;
k=1;
while (k<num)
{
if (a1[i]<a2[j])//取最小值
{
w=a1[i];
i++;
}
else
{
w=a2[j];
j++;
}
if (a1[i]<a2[j])//取第二次
{
w+=a1[i];
i++;
}
else
{
w+=a2[j];
j++;
}
a2[++n2]=w;//加入第二个队列
k++;//计算合并次数
sum+=w;//计算价值
}
printf("%d",sum);
}
#include
using namespace std;
int count = 0;//记录逆序对的个数
// 合并数组,排好序,然后在拷贝到原来的数组array
void MergeArray(int array[], int start, int end ,int mid, int temp[]) {
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end ) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
}else {
temp[k++] = array[j++];
count += mid - i + 1;
}
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= end) {
temp[k++] = array[j++];
}
for (int i = 0; i < k; i ++) {
array[start + i] = temp[i];
}
}
// 归并排序,将数组前半部分后半部分分成最小单元,然后在合并
void MergeSort(int array[], int start, int end, int temp[]) {
if(start < end) {
int mid = (start + end)/ 2;
MergeSort(array, start, mid, temp);
MergeSort(array, mid + 1, end, temp);
MergeArray(array, start, end, mid, temp);
}
}
// 在这里创建临时数组,节省内存开销,因为以后的temp都是在递归李使用的。
void MergeSort(int array[], int len) {
int start = 0;
int end = len - 1;
int *temp = new int[len];
MergeSort(array, start, end, temp);
}
void PrintArray(int array[], int len) {
for (int i = 0 ; i < len; ++i) {
cout << array[i] << " " ;
}
cout << endl;
}
int main(int argc, const char * argv[]) {
int array[] = {3,5,3,6,7,3,7,8,1};
MergeSort(array, 9);
PrintArray(array, 9);
return 0;
}
参考题目链接:
POJ 3368 Frequent values
/*
* 求区间中数出现的最大频率
* 方法一:线段树.
* 先离散化。因为序列是升序,所以先将所有值相同的点缩成一点。这样n规模就缩小了。建立一个数据结构
* 记录缩点的属性:在原序列中的值id,和该值有多少个num比如序列
* 10
* -1 -1 1 1 1 1 3 10 10 10
* 缩点后为:下标 1 2 3 4
* id -1 1 3 10
* num 2 4 1 3
* 然后建树,树的属性有区间最大值(也就是频率)和区间总和。
* 接受询问的时候。接受的是原来序列的区间[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(int index, int a, int b);
const int N = 100010;
struct NODE
{
int b, e; // 区间[b, e]
int l, r; // 左右子节点下标
int number; // 区间内的最大频率值
int last; // 以 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[]
}
}
return 0;
}
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(int index, 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;
}
如果只是交换相邻两数,那么最少交换次数为该序列的逆序数。
交换任意两数
/*
* 交换任意两数的本质是改变了元素位置,
* 故建立元素与其目标状态应放置位置的映射关系
*/
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';
return 0;
}
交换任意区间
/*
* 默认目标映射关系是 key 1 => val 1 …… key n => val n
* 如果序列不是 1~n 可以通过 map 建立新的目标映射关系
* 交换任意区间的本质是改变了元素的后继,故建立元素与其初始状态后继的映射关系
*/
const int 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';
return 0;
}
/*
* 动态规划:f(n,m)表示逆序数为m的n元排列的个数,则
* f(n + 1, m) = f(n, m) + f(n, m - 1) + ... + f(n, m - n)(当 b<0 时,f(a,b) = 0)
* 优化又考虑到如果直接利用上式计算时间复杂度为O(n^3),我们分析上
* 式不难发现f(n + 1, m) = f(n, m) + f(n + 1, m - 1)
* end = node[index].e,
* if(m-n-1 >= 0) f(n+1, m) -= f(n, m-n-1).
*/
const int N = 1001;
const int C = 10001;
const long MOD = 1000000007;
long arr[N][C];
long long temp;
int main()
{
int i, j;
arr[1][0] = arr[2][0] = arr[2][1] = 1;
for (i = 3; i < N; ++i)
{
arr[i][0] = 1;
long h = i * (i + 1) / 2 + 1;
if (h > C)
{
h = C;
}
for (j = 1; j < h; ++j)
{
temp = arr[i - 1][j] + arr[i][j - 1];
arr[i][j] = temp % MOD;
if (j - i >= 0)
{
arr[i][j] -= arr[i - 1][j - i];
if (arr[i][j] < 0)
{ // 注意:由于arr[i][j]和arr[i - 1][j - i]都是模过的,所以可能会得到负数
arr[i][j] += MOD;
}
}
}
}
while (scanf("%d %d", &i, &j) != EOF)
{
printf("%ld\n", arr[i][j]);
}
return 0;
}
#include
#include
#include
using namespace std;
const int maxn=1e6+7;
int next[maxn];
char s2[maxn],s1[maxn];
void spawn_next(char* st)
{
int l=strlen(st),k=0;
next[0]=-1;
for(int i=0; i<l; i++)
{
k=next[i];
while(k!=-1&&st[i]!=st[k]) k=next[k];
next[i+1]=++k;
}
}
int match(char* st,char* s)
{
int l1=strlen(st),l2=strlen(s);
int k=0;
for(int i=0; i<l1; i++)
{
while(k!=-1&&st[i]!=s[k]) k=next[k];
if(st[i]==s[k]) k++;
if(k==l2) return i-l2+1;
}
}
int kmp(char* st,char* s)
{
spawn_next(st); return match(st,s);
}
int main()
{
scanf("%s%s",s1,s2);
int ass=kmp(s1,s2)+1;
printf("%d",ass);
}
void get_next()
{//next数组保存了以i结尾的字符串的最长公共前缀和后缀的起始坐标
int i,j;
next[0] = j = -1;
i = 0;
while(i < l2)
{
while(j!=-1&&str2[j]!=str2[i])//自身和自身进行匹配
j = next[j];
next[++i] = ++j;
}
}
int kmp()
{
int i,j;
i = j = 0;
while(i < l1&&j<l2)
{
while(j!=-1&&str1[i]!=str2[j])
{
j = next[j];
}
i++;
j++;
}
if(j == l2)
return i-j;//完全匹配时的开始下标,下标从0开始
return -1;//不存在匹配情况
}
int kmp()
{
int i,j;
i = j = 0;
while(i < l1)//注意和返回下标的区别
{
while(j!=-1&&str1[i]!=str2[j])
{
j = next[j];
}
if(j == l2-1)
{
ans ++;
j = next[j];
}
i++;
j++;
}
return ans;//返回匹配次数
}
#include //数据流输入/输出
#include //STL 通用算法
#include //STL迭代器
#include //基本输出流
#include //STL 队列容器
#include //STL 集合容器
#include //基于字符串的流
#include //STL 堆栈容器
#include //所需头文件
transform(str.begin(),str.end(),str.begin(),tolower); //将string转化为小写
transform(s.begin(), s.end(), s.begin(), toupper); //将string转化为大写
#include
#include
#include
using namespace std;
void mytolower(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='A'&&s[i]<='Z'){
s[i]=tolower(s[i]);
//s[i]+=32;//+32转换为小写
//s[i]=s[i]-'A'+'a';
}
}
}
void mytoupper(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='a'&&s[i]<='z'){
s[i]=toupper(s[i]);
//s[i]-=32;//+32转换为小写
//s[i]=s[i]-'a'+'A';
}
}
}
int main() {
cout<<"请输入一个含大写的字符串:";
char s[201];
gets(s);
///转小写
mytolower(s);
cout<<"转化为小写后为:"<<s<<endl;
mytoupper(s);
cout<<"转化为大写后为:"<<s<<endl;
return 0;
}
void string_replace(string &str, const string old0, const string new0)
{
string::size_type nPos = 0;
string::size_type nsrclen = old0.size();
string::size_type ndstlen = new0.size();
while(nPos = str.find(old0, nPos))
{
if(nPos == string::npos) break;
str.replace(nPos, nsrclen, new0);
nPos += ndstlen;
}
}
#include
#include
#include
using namespace std;
vector<string> split(const string &s, const string &seperator){
vector<string> result;
typedef string::size_type string_size;
string_size i = 0;
while(i != s.size()){
//找到字符串中首个不等于分隔符的字母;
int flag = 0;
while(i != s.size() && flag == 0){
flag = 1;
for(string_size x = 0; x < seperator.size(); ++x)
if(s[i] == seperator[x]){
++i;
flag = 0;
break;
}
}
//找到又一个分隔符,将两个分隔符之间的字符串取出;
flag = 0;
string_size j = i;
while(j != s.size() && flag == 0){
for(string_size x = 0; x < seperator.size(); ++x)
if(s[j] == seperator[x]){
flag = 1;
break;
}
if(flag == 0)
++j;
}
if(i != j){
result.push_back(s.substr(i, j-i));
i = j;
}
}
return result;
}
int main(){
string s = "a,b*c*d,e";
vector<string> v = split(s, ",*"); //可按多个字符来分隔;
for(vector<string>::size_type i = 0; i != v.size(); ++i)
cout << v[i] << " ";
cout << endl;
//输出: a b c d
}
方法2:
#include
#include
vector<string> split(const string &str, const string &delim)
{
vector<string> res;
if("" == str) return res;
char *strs = new char[str.length()+1];
strcpy(strs,str.c_str());
char *d = new char[delim.length()+1];
strcpy(d,delim.c_str());
char *p = strtok(strs,d);
while(p)
{
string s = p;
res.push_back(s);
p = strtok(NULL,d);
}
return res;
}
#include
int to_int(const string& a)
{
int res;
stringstream ss;
ss << a;
ss >> res;
return res;
}
#include
string to_str(int a)
{
string res;
stringstream ss;
ss << a;
ss >> res;
return res;
}
#include
str = strupr( str );
#include
str = strlwr( str );
bool isleapyear(int y)
{
return (y%4==0&&y%100)||y%400==0;
}
string getWeekday(string year,string month,string day)
{
int y=stoi_x(year),m=stoi_x(month),d=stoi_x(day);
int by=1970,countday=0;
while(by<y)
{
countday+=(isleapyear(by))?366:365;
++by;
}
for(int i=1;i<m;++i) countday+=mtharray[i];
countday+=d-1;
return "0"+to_string_x((4+countday%7)%7);
}
char s[100];
cin.getline(s , 100); //read a line, discard \n
cin.get(s,100)//read a line, leave \n in queue
#include //没有该头文件,iostream也可以
void toStandard(string &str)//注意:是对原string进行修改
{
int len = str.size( );
for(int i = 0;i <len;i++)
str[i] = tolower( str[i] );
}
sscanf(“2006:03:18 - 2006:04:18”, “%[0-9,:] -%[0-9,:]”, sztime1, sztime2);
3.C/C++标准库函数sprintf和printf:
sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致sprintf 比printf 有用得多.下面我们先说以下sprintf的用法.
sprintf 是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... )
int sprintf( char *buffer, const char *format [, argument] ... )
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:格式化字符串上。
printf 和sprintf 都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串。
sprintf最常见的应用之一是把整数打印到字符串中.例如:
//把整数123 打印成一个字符串保存在s 中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, “%-8d%8d”, 123, 4567); //产生:“123 4567”
也可以按照16 进制打印:
sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐
sprintf(s, “%-8X”, 4568); //大写16 进制,宽度占8 个位置,左对齐
这样,一个整数的16 进制字符串就很容易得到,但我们在打印16 进制内容时,通常想要一种左边补0 的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0 就可以了。sprintf(s, “%08X”, 4567); //产生:“000011D7”
上面以”%d”进行的10 进制打印同样也可以使用这种左边补0 的方式。
这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1 的内存16 进制表
示形式,在Win32 平台上,一个short 型占2 个字节,所以我们自然希望用4 个16 进制数字来打
印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf 是个变参函数,除了前面两个参数之外,后面的
参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈
时被压进来的到底是个4 字节的整数还是个2 字节的短整数,所以采取了统一4 字节的处理方式,
导致参数压栈时做了符号扩展,扩展成了32 位的整数-1,打印时4 个位置不够了,就把32 位整数
-1 的8 位16 进制都打印出来了。如果你想看si 的本来面目,那么就应该让编译器做0 扩展而不是
符号扩展(扩展时二进制左边补0 而不是补符号位):
sprintf(s, “%04X”, (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, “%04X”, si);
sprintf 和printf 还可以按8 进制打印整数字符串,使用”%o”。注意8 进制和16 进制都不会打
印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16 进制或8 进制表示。
控制浮点数打印格式
浮点数的打印和格式控制是sprintf 的又一大常用功能,浮点数使用格式符”%f”控制,默认保
留小数点后6 位数字,比如:
sprintf(s, “%f”, 3.1415926); //产生"3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:”%m.nf”格式,其中m 表
示打印的宽度,n 表示小数点后的位数。比如:
sprintf(s, “%10.3f”, 3.1415626); //产生:" 3.142"
sprintf(s, “%-10.3f”, 3.1415626); //产生:"3.142 "
sprintf(s, “%.3f”, 3.1415626); //不指定总宽度,产生:“3.142”
注意一个问题,你猜
int i = 100;
sprintf(s, “%.2f”, i);
会打出什么东东来?“100.00”?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, “%.2f”, (double)i);
第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i
相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道当年被压入栈里的是个整数,
于是可怜的保存整数i 的那4 个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。
不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手
工编排的结果是否正确。
字符/Ascii 码对照
我们知道,在C/C++语言中,char 也是一种普通的scalable 类型,除了字长之外,它与short,
int,long 这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把
这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte 或short 来把char 通过typedef 定
义出来,这样更合适些)
于是,使用”%d”或者”%x”打印一个字符,便能得出它的10 进制或16 进制的ASCII 码;反过
来,使用”%c”打印一个整数,便可以看到它所对应的ASCII 字符。以下程序段把所有可见字符的
ASCII 码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16 进制数增加”0X”
前缀):
for(int i = 32; i < 127; i++) {
printf("[ %c ]: %3d 0x%#04X\n", i, i, i);
}
连接字符串
sprintf 的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连
接字符串,从而在许多场合可以替代strcat,但sprintf 能够一次连接多个字符串(自然也可以同时
在它们中间插入别的内容,总之非常灵活)。比如:
char* who = “I”;
char* whom = “CSDN”;
sprintf(s, “%s love %s.”, who, whom); //产生:"I love CSDN. "
strcat 只能连接字符串(一段以’\0’结尾的字符数组或叫做字符缓冲,null-terminated-string),
但有时我们有两段字符缓冲区,他们并不是以’\0’结尾。比如许多从第三方库函数中返回的字符数
组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’\0’来结
尾。如果直接连接,不管是sprintf 还是strcat 肯定会导致非法内存操作,而strncat 也至少要求第
一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数
时可以指定宽度,字符串也一样的。比如:
char a1[] = {‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’};
char a2[] = {‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’};
如果:
sprintf(s, “%s%s”, a1, a2); //Don’t do that!
十有八九要出问题了。是否可以改成:
sprintf(s, “%7s%7s”, a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, “%.7s%.7s”, a1, a2);//产生:“ABCDEFGHIJKLMN”
这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m 表示占用宽度(字符串长度不足时补空
格,超出了则按照实际宽度打印),n 才表示从相应的字符串中最多取用的字符数。通常在打印字
符串时m 没什么大用,还是点号后面的n 用的多。自然,也可以前后都只取部分字符:
sprintf(s, “%.6s%.5s”, a1, a2);//产生:“ABCDEFHIJKL”
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是
静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种
动态的宽度/精度设置功能在sprintf 的实现中也被考虑到了,sprintf 采用”*”来占用一个本来需要一
个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一
样被提供出来,于是,上面的例子可以变成:
sprintf(s, “%.*s%.*s”, 7, a1, 7, a2);
或者:
sprintf(s, “%.*s%.s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, “%-d", 4, ‘A’); //产生"65 "
sprintf(s, "%#0X”, 8, 128); //产生"0X000080","#"产生0X
sprintf(s, "%.f", 10, 2, 3.1415926); //产生" 3.14"
打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32 位的数,你完全可以使用打印无符号整数的”%u”把他们打印出来:
sprintf(s, “%u”, &i);
不过通常人们还是喜欢使用16 进制而不是10 进制来显示一个地址:
sprintf(s, “%08X”, &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:
sprintf(s, “%p”, &i);
我觉得它实际上就相当于:
sprintf(s, "%0x”, 2 * sizeof(void *), &i);
利用sprintf 的返回值
较少有人注意printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用
最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf 调用结束以后,你无须再调用一次
strlen 便已经知道了结果字符串的长度。如:
int len = sprintf(s, “%d”, i);
对于正整数来说,len 便等于整数i 的10 进制位数。
下面的是个完整的例子,产生10 个[0, 100)之间的随机数,并将他们打印到一个字符数组s 中,以逗号分隔开。
#include
#include
#include
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, "%d,", rand() % 100);
}
s[offset - 1] = '\n';//将最后一个逗号换成换行符。
printf(s);
return 0;
}
通配符
%a(%A) 浮点数、十六进制数字和p-(P-)记数法(C99)
%c 表示以(ASCII)字符
%d 表示将输出值以整数对待(符号十进制整数) %md,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出
%s 字符串
%f 表示以32bit浮点数(包括float和doulbe)
%e(%E) 浮点数指数输出[e-(E-)记数法]
%g(%G) 浮点数不显无意义的零"0"
%i 有符号十进制整数(与%d相同)
%u 无符号十进制整数
%o 八进制整数 e.g. 0123
%x(%X) 十六进制整数0f(0F) e.g. 0x1234
%p 指针
%% "%"
备注:c语言,%d %.2d %2d%02d的区别
%d就是普通的输出了
%2d是将数字按宽度为2,采用右对齐方式输出,若数据位数不到2位,则左边补空格
%02d,和%2d差不多,只不过左边补0
%.2d没见过,但从执行效果来看,和%02d一样
2.标志
左对齐:"-" e.g. “%-20s”
右对齐:"+" e.g. “%+20s”
空格:若符号为正,则显示空格,负则显示"-" e.g. “% 6.2f”
#:对c,s,d,u类无影响;对o类,在输出时加前缀o;对x类,在输出时加前缀0x;
对e,g,f 类当结果有小数时才给出小数点。
3.格式字符串(格式)
〔标志〕〔输出最少宽度〕〔.精度〕〔长度〕类型
“%-md” :左对齐,若m比实际少时,按实际输出。
“%m.ns”:输出m位,取字符串(左起)n位,左补空格,当n>mor m省略时m=n
e.g. “%7.2s” 输入CHINA
#include //所需头文件
transform(str.begin(),str.end(),str.begin(),::tolower); //将string转化为小写
transform(s.begin(), s.end(), s.begin(), ::toupper); //将string转化为大写
#include
#include
#include
using namespace std;
void mytolower(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='A'&&s[i]<='Z'){
s[i]=tolower(s[i]);
//s[i]+=32;//+32转换为小写
//s[i]=s[i]-'A'+'a';
}
}
}
void mytoupper(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='a'&&s[i]<='z'){
s[i]=toupper(s[i]);
//s[i]-=32;//+32转换为小写
//s[i]=s[i]-'a'+'A';
}
}
}
int main() {
cout<<"请输入一个含大写的字符串:";
char s[201];
gets(s);
///转小写
mytolower(s);
cout<<"转化为小写后为:"<<s<<endl;
mytoupper(s);
cout<<"转化为大写后为:"<<s<<endl;
return 0;
}
void string_replace(string &str, const string old0, const string new0)
{
string::size_type nPos = 0;
string::size_type nsrclen = old0.size();
string::size_type ndstlen = new0.size();
while(nPos = str.find(old0, nPos))
{
if(nPos == string::npos) break;
str.replace(nPos, nsrclen, new0);
nPos += ndstlen;
}
}
方法1(多个分割符):
#include
#include
#include
using namespace std;
vector<string> split(const string &s, const string &seperator){
vector<string> result;
typedef string::size_type string_size;
string_size i = 0;
while(i != s.size()){
//找到字符串中首个不等于分隔符的字母;
int flag = 0;
while(i != s.size() && flag == 0){
flag = 1;
for(string_size x = 0; x < seperator.size(); ++x)
if(s[i] == seperator[x]){
++i;
flag = 0;
break;
}
}
//找到又一个分隔符,将两个分隔符之间的字符串取出;
flag = 0;
string_size j = i;
while(j != s.size() && flag == 0){
for(string_size x = 0; x < seperator.size(); ++x)
if(s[j] == seperator[x]){
flag = 1;
break;
}
if(flag == 0)
++j;
}
if(i != j){
result.push_back(s.substr(i, j-i));
i = j;
}
}
return result;
}
int main(){
string s = "a,b*c*d,e";
vector<string> v = split(s, ",*"); //可按多个字符来分隔;
for(vector<string>::size_type i = 0; i != v.size(); ++i)
cout << v[i] << " ";
cout << endl;
//输出: a b c d
}
方法2:
#include
#include
vector<string> split(const string &str, const string &delim)
{
vector<string> res;
if("" == str) return res;
char *strs = new char[str.length()+1];
strcpy(strs,str.c_str());
char *d = new char[delim.length()+1];
strcpy(d,delim.c_str());
char *p = strtok(strs,d);
while(p)
{
string s = p;
res.push_back(s);
p = strtok(NULL,d);
}
return res;
}
#include
str = strupr( str );
#include
str = strlwr( str );
int Isprime(int n)
{
for(int i=2;i*i<=n;i++)
if(n%i==0) return 0;
return 1;
}
求n以内的素数,如果 i 不为素数就将a[ i ]设为1,否则就设为0。
#include
#include
#include
using namespace std;
int a[(int)1e8+5];
void prime(int n)
{
memset(a,0,sizeof(a));
for(int i=2;i<=n;i++)
for(int j=2;j*i<=n;j++)
if(!a[i*j]) a[i*j]=!a[i*j];
for(int i=2;i<=n;i++)
if(!a[i]) printf("%-5d",i);
}
int main()
{
int n;
while(~scanf("%d",&n))
prime(n);
return 0;
}
#include
double pow(double x,double y) //求x的y次方
double exp (double x); //求e的x次幂的值
typedef long long LL; // 视数据大小的情况而定
LL powerMod(LL x, LL n, LL m) x的n次方,结果模m
{
LL res = 1;
while (n > 0){
if (n & 1) // 判断是否为奇数,若是则true
res = (res * x) % m;
x = (x * x) % m;
n >>= 1; // 相当于n /= 2;
}
return res;
}
#include
int gcd(int a,int b) // 返回a , b的最大公约数
{
return !b?a:gcd(b,a%b);
}
int main()
{
int a,b;
while(scanf("%d %d",&a,&b)!=EOF)
printf("%d\n",gcd(a,b));
return 0;
}
/*
大数加法采用的是模拟的思想,就是利用数组来储存一个数的每一位
*/
#include
#include
#include
using namespace std;
const int MAXN=10000;
char s1[MAXN],s2[MAXN];
int n1[MAXN],n2[MAXN],sum[MAXN];
int main()
{
int T;
scanf("%d",&T);
for(int k=1;k<=T;k++)
{
scanf("%s %s",s1,s2);//用字符串来储存两个大数
memset(n1,0,sizeof(n1));//初始化数组,让它们全为0
memset(n2,0,sizeof(n2));
memset(sum,0,sizeof(sum));//初始化保存结果的数组
int len1=strlen(s1);//第一个数的长度
int len2=strlen(s2);//第二个数的长度
int j=0;
for(int i=len1-1;i>=0;i--)//将第一个数的每一位都逆序赋值给第一个数组
n1[j++]=s1[i]-'0';
j=0;
for(int i=len2-1;i>=0;i--)
n2[j++]=s2[i]-'0';
int len=len1>len2?len1:len2;//找出来两个数中比较长的那个数
int pre=0;//用来保存进位
for(int i=0;i<len;i++)//给sum赋值,要记得sum可能是大于9的,输出的时候要对10取余
{
sum[i]=n1[i]+n2[i]+pre/10;
pre=sum[i];
}
if(pre>9)//保存最高位的是sum[len-1] ,如果大于9 ,结果的位数要加一
{
sum[len]=pre/10;//取高位
len++;//位数 +1
}
int t=len;
for(int i=len-1;i>=0;i--) //去掉前置0
{
if(sum[i]==0) t--;//像 0001 + 3 这种,结果应该输出 4 而不是0004
else break;
}
printf("Case %d:\n%s + %s = ",k,s1,s2);
for(int i=t-1;i>=0;i--)
printf("%d",sum[i]%10);
printf("\n");
if(k!=T) printf("\n");//最后一组数据不要空行
}
return 0;
}
/*
大数乘法:
运用模拟,两个数组来储存两个大数
另外一个数组来保存运算结果
em....
*/
#include
#include
#include
using namespace std;
const int MAXN=10000;
char s1[MAXN],s2[MAXN];
int a[MAXN],b[MAXN],c[MAXN];
int main()
{
while(scanf("%s %s",s1,s2)!=EOF)
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
int len1=strlen(s1);//第一个数的长度
int len2=strlen(s2);//第二个数的长度
int i,j;
for(i=len1-1,j=0;i>=0;i--) a[j++]=s1[i]-'0';//用数组逆序保存第一个数
for(i=len2-1,j=0;i>=0;i--) b[j++]=s2[i]-'0';//用数组逆序保存第二个数
for(i=0;i<len1;i++)
for(j=0;j<len2;j++)
c[j+i]+=a[i]*b[j];//一定要 + *
int len=len1+len2;
for(i=0;i<len;i++)//进行进位运算
if(c[i]>9)
{
c[i+1]+=c[i]/10;
c[i]%=10;
}
int t=len;
for(i=len-1;i>=0;i--)//去掉前置0
if(c[i]==0) t--;
else break;
for(i=t-1;i>=0;i--)//输出
printf("%d",c[i]);
printf("\n");
}
return 0;
}
#include
template<typename item>
class smallest_heap{
private:
item heap[10001];
int len;
public:
smallest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
smallest_heap<item>::smallest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void smallest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]<heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void smallest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]>heap[son+1]) son++;
if(heap[father]>heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item smallest_heap<item>::top(){
return heap[1];
}
template<typename item>
int smallest_heap<item>::size(){
return len;
}
template<typename item>
bool smallest_heap<item>::empty(){
return len;
}
#include
template<typename item>
class largest_heap{
private:
item heap[10001];
int len;
public:
largest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
largest_heap<item>::largest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void largest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]>heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void largest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]<heap[son+1]) son++;
if(heap[father]<heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item largest_heap<item>::top(){
return heap[1];
}
template<typename item>
int largest_heap<item>::size(){
return len;
}
template<typename item>
bool largest_heap<item>::empty(){
return len;
}
同时也可以支持自己编写的类,但须提供“<”或“>”的运算符重载,例如
class T{
private:
int a;
public:
bool operator<(T const &type){
return a<type.a;
}
};
smallest_heap<T> heap;
例题:P1090 合并果子 - 洛谷
时间复杂度:O(n)
#include
#include
#include
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
scanf("%d",&num);
memset(a1,127/3,sizeof(a1));
memset(a2,127/3,sizeof(a2));
for (int i=1;i<=num;i++)
{
scanf("%d",&x);
t[x]++;//桶
}
for (int i=1;i<=20000;i++)
{
while (t[i])//桶排序
{
t[i]--;
a1[++n1]=i;
}
}
int i=1,j=1;
k=1;
while (k<num)
{
if (a1[i]<a2[j])//取最小值
{
w=a1[i];
i++;
}
else
{
w=a2[j];
j++;
}
if (a1[i]<a2[j])//取第二次
{
w+=a1[i];
i++;
}
else
{
w+=a2[j];
j++;
}
a2[++n2]=w;//加入第二个队列
k++;//计算合并次数
sum+=w;//计算价值
}
printf("%d",sum);
}
#include
using namespace std;
int count = 0;//记录逆序对的个数
// 合并数组,排好序,然后在拷贝到原来的数组array
void MergeArray(int array[], int start, int end ,int mid, int temp[]) {
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end ) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
}else {
temp[k++] = array[j++];
count += mid - i + 1;
}
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= end) {
temp[k++] = array[j++];
}
for (int i = 0; i < k; i ++) {
array[start + i] = temp[i];
}
}
// 归并排序,将数组前半部分后半部分分成最小单元,然后在合并
void MergeSort(int array[], int start, int end, int temp[]) {
if(start < end) {
int mid = (start + end)/ 2;
MergeSort(array, start, mid, temp);
MergeSort(array, mid + 1, end, temp);
MergeArray(array, start, end, mid, temp);
}
}
// 在这里创建临时数组,节省内存开销,因为以后的temp都是在递归李使用的。
void MergeSort(int array[], int len) {
int start = 0;
int end = len - 1;
int *temp = new int[len];
MergeSort(array, start, end, temp);
}
void PrintArray(int array[], int len) {
for (int i = 0 ; i < len; ++i) {
cout << array[i] << " " ;
}
cout << endl;
}
int main(int argc, const char * argv[]) {
int array[] = {3,5,3,6,7,3,7,8,1};
MergeSort(array, 9);
PrintArray(array, 9);
return 0;
}
算法导论p.223
#include
#include
#include
using namespace std;
const int maxn=1009;
char a[maxn],b[maxn];
int path[maxn][maxn],dp[maxn][maxn];//path 记录路径
void lcs(int i,int j)//打印路径
{
if(i==0||j==0) return ;//结束标志,a或者b只要有一个找完了,就不在找了
if(path[i][j]==1)//path是1的时候输出这个字符
{
lcs(i-1,j-1);//因为是从后往前找的
printf("%c",a[i-1]);//所以这句得写到递归函数下边
}
else if(path[i][j]==2)
lcs(i-1,j);
else
lcs(i,j-1);
return ;
}
int main()
{
while(~scanf("%s %s",a,b))
{
memset(dp,0,sizeof(dp));
int m=strlen(a);
int n=strlen(b);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
path[i][j]=1;
}
else if(dp[i-1][j]>dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
path[i][j]=2;
}
else
{
dp[i][j]=dp[i][j-1];
path[i][j]=3;
}
lcs(m,n);
printf("\n");
// printf("\n%d\n",dp[m][n]);//输出最长子序列的长度
}
return 0;
public void lis(float[] L)
{
int n = L.length;
int[] f = new int[n];//用于存放f(i)值;
f[0]=1;//以第a1为末元素的最长递增子序列长度为1;
for(int i = 1;i<n;i++)//循环n-1次
{
f[i]=1;//f[i]的最小值为1;
for(int j=0;j<i;j++)//循环i 次
{
if(L[j]<L[i]&&f[j]>f[i]-1)
f[i]=f[j]+1;//更新f[i]的值。
}
}
System.out.println(f[n-1]);
}
#include
using namespace std;
const int MAXN=105;
int a[MAXN];
int dp[MAXN];
int n;
int vis[MAXN];
void dfs(int pos)//打印路径
{
if(pos==-1) return ;
dfs(vis[pos]);
printf(" %d",pos+1);//这里是正序输出编号(从1开始的)
}
int main()
{
while(~scanf("%d",&n)&&n)
{
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
memset(vis,-1,sizeof(vis));
int res=0;
int pos=-1;
for(int i=0;i<n;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(a[i]>a[j])
{
if(dp[i]<dp[j]+1)
{
dp[i]=dp[j]+1;
vis[i]=j;//vis[i]=j 表示以a[i]为结尾的LIS的上一个元素是a[j]
}
}
if(res<dp[i])
{
res=dp[i];
pos=i;//找到LIS的最后一个结点
}
}
printf("The number is %d:",res);//输出LIS的长度
dfs(pos);
printf("\n");
}
return 0;
}
Dijkstra
1.定义概览
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。
问题描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)
2.算法描述
1)算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
2)算法步骤:
a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则正常有权值,若u不是v的出边邻接点,则权值为∞。
b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d.重复步骤b和c直到所有顶点都包含在S中。
prim和dijkstra大同小异,唯一的区别是prim是维护树到其余节点的最小路径,而dijkstra是维护源节点到各个节点的最小路径。
例题:- 计算机软件能力认证考试系统 ccf201812-4
/*
Kruskal算法求MST
*/
#include
#include
#include
#include
#include
using namespace std;
const int MAXN=1000000;//最大点数
const int MAXM=1000000;//最大边数
int F[MAXN];//并查集使用 查找使用的,可以改为用set来记录在A中的结点(相关概念见算法导论)
struct Edge
{
int u,v,w;
}edge[MAXM];//储存边的信息,包括起点/终点/权值
int tol;//边数,加边前赋值为0
void addedge(int u,int v,int w)
{
edge[tol].u=u;
edge[tol].v=v;
edge[tol++].w=w;
}
bool cmp(Edge a,Edge b)//排序函数,边按照权值从小到大排序
{
return a.w<b.w;
}
int Find(int x)
{
if(F[x]==-1)
return x;
else
return F[x]=Find(F[x]);//平均时间复杂度为常数级
}
int Kruskal(int n)//传入点数,返回最小生成树的权值,如果不连通返回-1
{
memset(F,-1,sizeof(F));
sort(edge,edge+tol,cmp);
int cnt=0;//计算加入的边数
int ans=0,maxedge = 0;
for(int i=0;i<tol;i++)
{
int u=edge[i].u;
int v=edge[i].v;
int w=edge[i].w;
int t1=Find(u);
int t2=Find(v);
if(t1!=t2)
{
ans+=w;
maxedge = max(maxedge,w);
F[t1]=t2;
cnt++;
}
if(cnt==n-1)//当添加的边数已经为n-1(n为结点数)时表示最小生成树已形成
break;
}
if(cnt<n-1)
return -1;//不连通
else
return maxedge;
}
int main( )
{
int N,m,root;
cin >> N;
cin >> m;
cin >> root;
for(int i = 0; i < m;i++)
{
int u,v,w;
cin >> u >> v >> w;
addedge(u,v,w);
}
cout<<Kruskal(N)<<endl;
return 0;
}
const int INF=0x3f3f3f3f;
const int maxn=1200;
int dist[maxn],g[maxn][maxn],N;// dist[i]表示源结点到i的最小距离,g[i][j]表示图中结点i到j的路径代价
bool vis[maxn];
void dijkstra()
{
for(int i=1;i<=N;i++)
dist[i]=(i==1)?0:INF;// 初始化,以i = 1 作为源结点
memset(vis,0,sizeof(vis));
for(int i=1;i<=N;i++)
{
int mark=-1,mindis=INF;
for(int j=1;j<=N;j++)
{
if(!vis[j]&&dist[j]<mindis)
{
mindis=dist[j];
mark=j;
}
}
vis[mark]=1;
for(int j=1;j<=N;j++)
{
if(!vis[j])
{
dist[j]=min(dist[j],g[mark][j]);//prim,求最小生成树
//dist[j]=min(dist[j],dist[mark]+g[mark][j]);//dijkstra,求单源最短路径
}
}
}
}
内存优化(但是时间复杂度高,70分):
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxN = 50010;//最大节点数
int dist[maxN], point[maxN], n, m;
bool vis[maxN];
std::vector<pair<int, int> > g[maxN];//g[i][j] = 为边(i , fi)的距离se;
void dijkstra(int root)
{
for(int i=1;i<=n;i++)
dist[i]=(i==root)?0:INF;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
int mark=-1,mindis=INF;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&dist[j]<mindis)
{
mindis=dist[j];
mark=j;
}
}
vis[mark]=1;
for(int j=0;j<g[mark].size();j++)
{
if(!vis[g[mark][j].first])
{
//dist[g[mark][j].fi]=min(dist[g[mark][j].fi],dist[mark]+g[mark][j].se);
int temax = max(dist[mark],g[mark][j].second);
dist[g[mark][j].first] = min(dist[g[mark][j].first],temax);
}
}
}
}
int main()
{
int m,root;
cin >> n;
cin >> m;
cin >> root;
for(int i = 0; i < m;i++)
{
int u,v,w;
cin >> u >> v >> w;
g[u].push_back(make_pair(v,w));
g[v].push_back(make_pair(u,w));
}
dijkstra(root);
int ans = 0;
for(int i = 1; i <= n;i++)
{
if(dist[i] != INF)
{
ans = max(ans,dist[i]);
}
}
cout << ans <<endl;
return 0;
}
堆优化(100分):
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int,int> pir;
//#define mem(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f;
const int maxN = 50010;//最大节点数
const int maxM = 1000000;//最大边数
int first[maxN],tot;
int vis[maxN],dist[maxN],n,m;
priority_queue <pir,vector<pir>,greater<pir>> q;
/*存放的内容与dist相同,都是当前树到其余节点的最小值,但是该方式的优先队列的对头总是权值最小的*/
struct edge
{
int v,w,next;
}e[maxM*2];
void add_edge(int u,int v,int w)
{
e[tot].v=v;
e[tot].w=w;
e[tot].next=first[u];//first用来记录上一个从u出发的边在e中的下标
first[u]=tot++;
}
void init()
{
memset(first, -1,sizeof(first));
tot=0;
memset(dist, INF, sizeof(dist));
}
int prim(int root)
{
int cnt=0,sum=0,maxedge=0;
dist[root]=0;
q.push(make_pair(0,root));
while(!q.empty()&&cnt<n)
{
int d=q.top().first,u=q.top().second;
q.pop();
maxedge = max(maxedge, d);
if(!vis[u])//遍历u连接的各个节点,更新dist
{
cnt++;
sum+=d;
vis[u]=1;
for(int i=first[u]; ~i; i=e[i].next)
//如果是求最小生成树,dist表示当前最小生成树到达各点的最小值
if(e[i].w + dist[u] < dist[e[i].v])
{
dist[e[i].v]=e[i].w + dist[u];
q.push(make_pair(dist[e[i].v],e[i].v));
}
//如果是求单源最短路径,dist表示根节点到达各点的最小值
if(e[i].w<dist[e[i].v])
{
dist[e[i].v]=e[i].w;
q.push(make_pair(dist[e[i].v],e[i].v));
}
}
}
return maxedge;
}
int main()
{
int u,v,w;
int root;
init();
scanf("%d%d%d",&n,&m,&root);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
cout << prim(root) <<endl;
return 0;
}
ps:还缺一个prim加edge的模板
优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
//算法判环并输出最短路径,边从0开始,点都可以
struct Edge
{
int from,to,dist;
};
vector<Edge> edges;
vector<int> g[1005];//存储from对应的边的标号
bool inq[1005];//是否在队列中
int d[1005];//源点到各个点的最短路
int pre[1005];//最短路的上一条弧
int cnt[1005];//进队次数
int n,m;//n点的个数,m边的个数
int a,b;//求a到b的最短路径并输出a到b的路径
void init()
{
for(int i=0;i<=n-1;i++)
g[i].clear();
edges.clear();
}
void addedge(int from,int to,int dist)//边从0开始
{
edges.push_back((Edge){from,to,dist});
int num=edges.size();
g[from].push_back(num-1);
}
bool spfa(int s)//若存在负环返回false
{
queue<int> q;
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
memset(d,0x3f,sizeof(d));
d[s]=0;
inq[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for(int i=0;i<g[u].size();i++)
{
Edge e=edges[g[u][i]];
if(d[e.to]>d[u]+e.dist)//松弛操作
{
d[e.to]=d[u]+e.dist;
pre[e.to]=g[u][i];
if(!inq[e.to])
{
q.push(e.to);
inq[e.to]=1;
if(++cnt[e.to]>n)//最多松弛n-1次,所有有负环
return false;//有负环
}
}
}
}
return true;
}
void print(int s)//输出源点a到s的最短路径
{
if(s==a)
return ;
print(edges[pre[s]].from);
cout<<edges[pre[s]].from<<" ";
}
int main()
{
cin>>n>>m;
init();
for(int i=0;i<=m-1;i++)
{
int a,b,c;
cin>>a>>b>>c;
addedge(a,b,c);
}
cin>>a>>b;
if(spfa(a))
{
cout<<d[b]<<endl;
print(b);
cout<<b<<endl;
}
else
cout<<-1<<endl;
}
题目:
给定一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]* k[1] * … *k[m]可能的最大乘积是多少?
例子:
例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:算法:动态规划、贪婪算法
动态规划:
贪心:
代码:
/*
方法一:DP
思路:首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀时,
我们有n-1种选择,也就是说第一段绳子的可能长度分别为1,2,3.....,n-1。因此f(n)=max(f(i)*f(n-i)),
其中0
/*
方法二:贪心+数学推理
n<2时,返回0;n=2时,返回1;n=3时,返回2
根据数学计算,当n>=5时,2(n-2)>n,3(n-3)>n,这就是说,将绳子剪成2和(n-2)或者剪成3和(n-3)时,
乘积大于不剪的乘积,因此需要把绳子剪成2或者3。并且3(n-3)>=2(n-2),也就是说,当n>=5时,
应该剪尽量多的3,可以使最后的乘积最大。对于长度是n的绳子,我们可以剪出n/3个3,剩余长度是1或者2,
如果余数是1,就可以把1和最后一个3合并成4,那么4剪出两个2得到的乘积是4,比1*3大,因此这种情况下,
需要将3的个数减少1,变成两个2;如果余数是2,那么无需做修改。
可以得到最大的乘积是:3^timesOf3 * 2^timesOf2
相比动态规划,计算更简便,但是需要一定的数学技巧。
*/
#include
#include
using namespace std;
typedef long long LL; // 视数据大小的情况而定
long long int dp(int n)
{
long long int f[n+1];//f[n]表示长度为n的最优解
f[0]=0;
f[1]=1;
f[2]=2;
f[3]=3;
for(int i = 4;i <= n;i++)
{
long long int maxf = i;
for(int j = 1;j <= i/2;j++)
{
maxf = max(f[j]*f[i-j],maxf);//把切割的若干段分为两份
}
f[i] = maxf;
}
return f[n];
}
LL power(LL x, LL n) //x的n次方,结果模m
{
LL res = 1;
while (n > 0){
if (n & 1) // 判断是否为奇数,若是则true
res = (res * x) ;
x = (x * x);
n >>= 1; // 相当于n /= 2;
}
return res;
}
long long int greedy(int n)
{
if(n <= 4)
return n;
int num3 = n/3;
int remain = n % 3;
if(remain == 0)
{
return power(3,num3);
}
if(remain == 1)
{
return power(3,num3-1)*4;
}
if(remain == 2)
{
return power(3,num3)*2;
}
}
int main()
{
int n;
cin >> n;
cout << dp(n) << endl;
cout << greedy(n);
return 0;
}
有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路、最短路或路径计数问题。
题目描述:
有n个矩形,每个矩形可以用两个整数a,b描述,表示它的长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a
矩形之间的"可嵌套"关系是一个典型的二元关系,二元关系可以用图来建模。如果矩形X可以嵌套在矩形Y里,我们就从X到Y连一条有向边。这个有向图是无环的,因为一个矩形无法直接或间接地嵌套在自己的内部。换句话说,它是一个DAG。这样,我们的任务便是求DAG上的最长路径。
方法:
1.构造图,找最长通路
#include "stdio.h"
#include "string.h"
#define maxn 1000+10
typedef struct { //矩形的数据结构,长、宽
int length;
int width;
}rectangle;
int G[maxn][maxn]; //DAG图的矩阵表示 G[i][j]表示rec[i]可转入rec[j]
int d[maxn],n; //d[i]顶点i的最长路径 ,以rec[i[为结尾的(最大的矩形)能装的矩形的个数
rectangle rec[maxn];
//打印出图的邻接矩阵,目的是确保建图正确无误
void print_Graph()
{
printf("|矩 形|");
for(int i=0;i<n;i++)
printf("%2d,%2d|",rec[i].length,rec[i].width);
printf("\n");
for(int i=0;i<n;i++){
for(int k=0;k<=n;k++)
printf("------");
printf("\n");
printf("|%2d,%2d|",rec[i].length,rec[i].width);
for(int j=0;j<n;j++){
printf(" %d |",G[i][j]);
}printf("\n");
}
}
//构造图
void createGraph()
{
memset(G,0,sizeof(G));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(rec[i].length>rec[j].length && rec[i].width>rec[j].width){
G[i][j]=1; //rec[i] 包含 rec[j]
}
}
}
}
//记忆化搜索程序
int dp(int i)
{
int& ans=d[i]; //为该表项声明一个引用,简化对它的读写操作。
if(ans>0) return ans;
ans=1;
for(int j=0;j<n;j++){
if(G[i][j]){
int tmp=dp(j);
ans=ans>tmp+1?ans:tmp+1;
}
}
return ans;
}
int main()
{
int N;
scanf("%d",&N);
while(N-->0)
{
int ans=0;
scanf("%d",&n);
for(int i=0;i<n;i++){
int tmp1,tmp2;
scanf("%d%d",&tmp1,&tmp2);
rec[i].length=tmp1>tmp2?tmp1:tmp2;
rec[i].width=tmp1<tmp2?tmp1:tmp2;
}
createGraph();
//初始化记忆数组
memset(d,0,sizeof(d));
for(int i=0;i<n;i++){
int tmp=dp(i);
ans=ans>tmp?ans:tmp;
}
printf("%d\n",ans);
}
return 0;
}
2. 按照宽进行排序,找最长上升子序列
#include "stdio.h"
#include "string.h"
#define N 1000+10
int a[N][2];
int b[N];
int main()
{
int n,m,result;
int i,j,temp,temp1,max;
scanf("%d",&n);
while(n--)
{
scanf("%d",&m);
for(i=1;i<=m;i++) //读入测试数据
{
scanf("%d%d",&temp,&temp1);
a[i][0]=temp<temp1?temp:temp1; //矩形的宽
a[i][1]=temp<temp1?temp1:temp; //矩形的长
}
for(i=1;i<=m;i++) //对矩形的宽进行由小到大排序
for(j=i+1;j<=m;j++) if(a[i][0]>a[j][0])
{temp=a[i][0];a[i][0]=a[j][0];a[j][0]=temp; temp1=a[i][1];a[i][1]=a[j][1];a[j][1]=temp1;}
//求长进行最长升序列求解
memset(b,0,sizeof(b)); result=0;
for(i=m;i>0;i--)
{
max=0;
for(j=i+1;j<=m;j++)
if(a[j][1]>a[i][1] && a[j][0]>a[i][0]) max=max>b[j]?max:b[j];
b[i]=max+1;
result=result>b[i]?result:b[i];
}
printf("%d\n",result);
}
return 0;
}
/**
LIS
给定一个序列.求最长上升子序列(lis)p1。
*/
#include
#include
using namespace std;
const int MAX = 1000;
int n;
int a[MAX];
int f[MAX];//f[i]用来存放以第i个结尾的最长上升序列,
//对于f状态的说明,见到的所有博客都是说0-i序列中的最长上升序列长度,这是不对的
/*普通的最长升序列
返回长度为n的数组a的最长升序列长度*/
int LIS_1(int a[],int n)
{
memset(f,0,sizeof(f));
f[0]=1;
int ans = 0;
for(int i = 1;i < n;i++)
{
//f[i]=1;
int maxf = 1;
for(int j = 0;j < i;j++)
{
if(a[i] > a[j])//若是最长非递减序列则改为a[i] >= a[j]
maxf = max(maxf, f[j]+1);
}
f[i] = maxf;
ans = max(ans, f[i]);
}
return ans;
}
//二分法,找到b[k]中大于num的最小值的下标
int binary(int b[],int k,int num)
{
if(num < b[1])
return 1;
int mid = 0;
for(int low = 1,high = k;low < high-1;)
{
mid=(low+high)/2;
if(b[mid] <= num)
low = mid;
else
high = mid;
}
return high;
}
int LIS_improve(int a[],int n)//时间复杂度:nlogn
{
int b[n];//b[k]是序列a[0:i-1]中所有长度为k的递增子序列中的最小结尾元素
b[1] = a[0];
int k = 1;
for(int i = 1;i < n;i++)
{
if(a[i] > b[k]) //若是最长非递减序列则改为a[i] >= b[k]
b[++k] = a[i];
else
{
b[binary(b,k,a[i])] = a[i];
}
}
}
/*指定了必须包含a[k]的最长升序列*/
int LIS_2(int a[],int k,int n)
{
memset(f,0,sizeof(f));
f[0]=1;
int ans = 0;
for(int i = 1;i < n;i++)
{
//f[i]=1;
int maxf = 1;
if(i <= k)
{
for(int j = 0;j < i;j++)
{
if(a[i] > a[j])
maxf = max(maxf, f[j]+1);
}
f[i] = maxf;
ans = max(ans, f[i]);
}
else
{
for(int j = k;j < i;j++)
{
if(s[i] > s[k] && s[i] > s[j])
maxf = max(maxf, f[j]+1);
}
f[i] = maxf;
ans = max(ans, f[i]);
}
}
return ans;
}
int main()
{
cin >> n;
for(int i = 0;i <n;i++)
{
cin >> a[i];
}
return 0;
}
给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
分析:
面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
解决办法:声明一个 大小为 m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值 ,那么我们可以很容易分析得出 m[i][j] 的计算方法,
(1). j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿
m[ i ][ j ] = m[ i-1 ][ j ]
(2). j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。
如果拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。
如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)
究竟是拿还是不拿,自然是比较这两种情况那种价值最大。
由此可以得到状态转移方程:
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制
价值数组v = {8, 10, 6, 3, 7, 2},
重量数组w = {4, 6, 2, 2, 5, 1},
背包容量C = 12时对应的m[i][j]数组。
(第一行和第一列为序号,其数值为0)
如m[2][6],在面对第二件物品,背包容量为6时我们可以选择不拿,那么获得价值仅为第一件物品的价值8,如果拿,就要把第一件物品拿出来,放第二件物品,价值10,那我们当然是选择拿。m[2][6]=m[1][0]+10=0+10=10;依次类推,得到m[6][12]就是考虑所有物品,背包容量为C时的最大价值。
#include
#include
using namespace std;
const int N=15;
int main()
{
int v[N]={0,8,10,6,3,7,2};
int w[N]={0,4,6,2,2,5,1};
int m[N][N];
int n=6,c=12;
memset(m,0,sizeof(m));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)//由于m[i][j]只跟m[i-1][]有关,所以循环的顺序和答案无关
{
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
cout<<m[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
到这一步,可以确定的是可能获得的最大价值,但是我们并不清楚具体选择哪几样物品能获得最大价值。
另起一个 x[ ] 数组,x[i]=0表示不拿,x[i]=1表示拿。
m[n][c]为最优值,如果m[n][c]=m[n-1][c] ,说明有没有第n件物品都一样,则x[n]=0 ; 否则 x[n]=1。当x[n]=0时,由x[n-1][c]继续构造最优解;当x[n]=1时,则由x[n-1][c-w[i]]继续构造最优解。以此类推,可构造出所有的最优解。
void traceback()
{
for(int i=n;i>1;i--)
{
if(m[i][c]==m[i-1][c])
x[i]=0;
else
{
x[i]=1;
c-=w[i];
}
}
x[1]=(m[1][c]>0)?1:0;
}
优化空间复杂度:
先考虑一下上面的状态转移方程如何实现,肯定有一个主循环i = 1…N,每次算出来二维数组dp[i][0…V]的所有值。那么如果只用一个数组f[0…V],能不能保证第i次循环结束后f[v]就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v-c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V…0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。伪代码如下:
for i in 0 ... N
for v = V ... 0
f[v] = max{f[v], f[v-c[i]] + w[i]}
模板:
/*
01背包问题
01背包问题的特点是,">每种物品仅有一件,可以选择放或不放。
01背包问题描述:
有N件物品和一个容量为V的背包。第i件物品的重量是c[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
*/
#include
#define N 1050017
int max(int x,int y)
{
int M;
M=x>y ? x : y;
return M;
}
int wei[N],val[N],f[N];
int main()
{
int i, j, n, m;
while(scanf("%d",&n)!=EOF)
{
scanf("%d", &m);
for(i=0; i<n; i++)
scanf("%d%d", &wei[i],&val[i]);//wei[i]为重量,val[i]为价值
for(i=0; i<n; i++)
{
for(j=m; j>=wei[i]; j--)
f[j] = max(f[j], f[j-wei[i]]+val[i]);
}
printf("%d\n",f[m]);
}
return 0;
}
//此代码为poj3624
题目
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价格是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
思路
这个问题类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已非取或不取两种,而且右取0件、取1件、取2件…等很多种。如果仍然按照01背包的思路,令dp[i][v]表示前i种物品恰好放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:
dp[i][v] = max{dp[i-1][v - k * c[i]] + k * w[i] | 0 <= k * c[i]<= v}
转化为01背包求解
最简单的想法是:考虑到第i种物品最多选V/c[i]件,于是可以把第i种物品转换为V/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。但是这样完全没有改进时间复杂度,但这毕竟给了我们将完全背包转换为01背包问题的思路:将一种物品拆成多件物品
O(VN)的算法
这个算法使用一维数组,先看伪代码:
for i = 1 ... N
for v = 0 ... V
f[v] = max{f[v], f[v-cost] + weight}
你会发现,这个伪代码与01背包的伪代码只有v的循环次序不同而已。为什么这样一改就行呢?首先,想想为什么01背包问题中要按照v=V…0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰好是每种物品可选无限件,所以在考虑“加选一件dii种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][c-v[i]],所以就可以并且必须采用v=0…V的顺序循环.
/**
* 完全背包问题
*/
#include
#include
#define INF 50000000
typedef struct coin {
int price, weight;
} coin;
void dynamicPackage(coin *coins, int n, int v)
{
if (v < 0) {
printf("This is impossible.\n");
return;
}
int i, j, *dp;
// 动态分配内存
dp = (int *)malloc(sizeof(int) * (v + 1));
// 初始化
dp[0] = 0;
for (i = 1; i <= v; i ++) dp[i] = INF;
// 完全背包问题
for (i = 1; i <= n; i ++) {
for (j = coins[i].weight; j <= v; j ++) {
dp[j] = (dp[j] < dp[j - coins[i].weight] + coins[i].price) ? dp[j] : dp[j - coins[i].weight] + coins[i].price;
}
}
if (dp[v] >= INF)
printf("This is impossible.\n");
else
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);
// 清理内存
free(dp);
dp = NULL;
}
int main(void)
{
int t, e, f, n, i;
coin *coins;
scanf("%d", &t);
while (t --) {
scanf("%d %d", &e, &f);
scanf("%d", &n);
// 接收货币
coins = (coin *)malloc(sizeof(coin) * (n + 1));
if (coins == NULL) exit(-1);
for (i = 1; i <= n; i ++) {
scanf("%d %d", &coins[i].price, &coins[i].weight);
}
// 完全背包
dynamicPackage(coins, n, f - e);
free(coins);
coins = NULL;
}
return 0;
}
题目
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
思路
多重背包问题的思路跟完全背包的思路非常类似,只是k的取值是有限制的,因为每件物品的数量是有限制的,状态转移方程为:
dp[i][v] = max{dp[i - 1][v - k * c[i]] + w[i] | 0 <=k <= n[i]}dp[i][v] = max{dp[i - 1][v - k * c[i]] + w[i] | 0 <=k <= n[i]}
代码:
#include
#include
typedef struct rice {
int price, weight, num;
} rice;
void dynamic(rice *rices, int m, int n)
{
int i, j, cur, k, **dp;
// 动态申请二维数组
dp = (int **)malloc(sizeof(int *) * (m + 1));
for (i = 0; i <= m; i ++)
dp[i] = (int *)malloc(sizeof(int) * (n + 1));
// 初始化
for (i = 0; i <= m; i ++)
for (j = 0; j <= n; j ++)
dp[i][j] = 0;
// 动态规划
for (i = 1; i <= m; i ++) {
for (j = 1; j <= n; j ++) {
for (k = 0; k <= rices[i].num; k ++) {
if (j - k * rices[i].price >= 0) {
cur = dp[i - 1][j - k * rices[i].price] + k * rices[i].weight;
dp[i][j] = dp[i][j] > cur ? dp[i][j] : cur;
} else {
break;
}
}
}
}
printf("%d\n", dp[m][n]);
for (i = 0; i <= m; i ++)
free(dp[i]);
}
int main(void)
{
int i, c, n, m;
rice rices[2010];
scanf("%d", &c);
while (c --) {
scanf("%d %d", &n, &m);
// 接收数据
for (i = 1; i <= m; i ++) {
scanf("%d %d %d", &rices[i].price, &rices[i].weight, &rices[i].num);
}
// 多重背包问题
dynamic(rices, m, n);
}
return 0;
}
相关博客:
区间DP:http://www.cnblogs.com/Alan-Luo/articles/8723278.html
原博客:https://blog.csdn.net/acdreamers/article/details/18039073
石子合并问题是最经典的DP问题。首先它有如下3种题型:
(1) 有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本问题实际上就是哈夫曼的变形。
例题:合并果子(https://www.luogu.org/problemnew/show/P1090 )
(2) 有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
分析:区间DP
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
变形:CCF-CSP 201612-4 压缩编码
(3) 问题(2)的是在石子排列是直线情况下的解法,如果把石子改为环形排列,又怎么做呢?
例题:石子合并(https://www.luogu.org/problemnew/show/P1880)
代码:
1.
思路:贪心,排序可以用STL的sort也可以用桶排序或小根堆,然后用两个数组分别存放排序后的石堆和合并之后的石堆,用i和j分别对这两个数组在比较中往后遍历。详见洛谷解析
#include
#include
#include
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
freopen("F:/ChromeDownload/testdata.in","r",stdin);
scanf("%d",&num);
memset(a1,127/3,sizeof(a1));
memset(a2,127/3,sizeof(a2));
for (int i=1;i<=num;i++)
{
scanf("%d",&x);
t[x]++;//桶
}
for (int i=1;i<=20000;i++)
{
while (t[i])//通排序
{
t[i]--;
a1[++n1]=i;
}
}
int i=1,j=1;
k=1;
while (k<num)
{
if (a1[i]<a2[j])//取最小值
{
w=a1[i];
i++;
}
else
{
w=a2[j];
j++;
}
if (a1[i]<a2[j])//取第二次
{
w+=a1[i];
i++;
}
else
{
w+=a2[j];
j++;
}
a2[++n2]=w;//加入第二个队列
k++;//计算合并次数
sum+=w;//计算价值
}
printf("%d",sum);
}
思路:区间DP, 从区间长度len为1开始,可保证当前的问题的子问题已求得最优值,时间复杂度O(n3),可用平行四边形优化为O(n2)
/**
* 区间DP
* */
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
int N,s[305];
int f[305][305], sum[305];//f[i][j]表示合并从i到j(包括i和j)所需要的费用
int dpmin()
{
memset(f,INF, sizeof(f));
for(int i = 0;i <= N;i++)
{
f[i][i] = 0;
}
for(int len = 2;len <= N;len++)
{
int j;
for(int i = 1;i+len-1 <= N;i++)
{
j = len+i-1;
for(int k = i;k < j;k++)
{
f[i][j] = min(f[i][j], f[i][k]+f[k+1][j] );
}
f[i][j] += sum[j]-sum[i-1];
cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
}
}
return f[1][N];
}
int dpmax()
{
memset(f,0, sizeof(f));
for(int i = 0;i <= N;i++)
{
f[i][i] = 0;
}
for(int len = 2;len <= N;len++)
{
int j;
for(int i = 1;i+len-1 <= N;i++)
{
j = len+i-1;
for(int k = i;k < j;k++)
{
f[i][j] = max(f[i][j], f[i][k]+f[k+1][j]);
}
f[i][j] += sum[j]-sum[i-1];
cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
}
}
return f[1][N];
}
int main()
{
scanf("%d",&N);
//memset(f,INF, sizeof(f));
memset(sum,0,sizeof(sum));
for(int i = 1; i <= N;i++)
{
scanf("%d",&(s[i]));
f[i][i] = 0;
sum[i] = sum[i-1]+s[i];
cout << "sum[" << i << "]: " << sum[i] << endl;
}
cout << dpmin() << endl;
cout << dpmax() << endl;
}
思路:与第2种相似,把数据规模扩大一倍,把循环的最终解看做F[i%n][(i+n)%n],其中 1 ≤ i ≤ n,即将原数组内容复制一遍即可,但区间长度仍要保证最长为n。
/**
* 区间DP
* */
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
int N,s[305];
int f[305][305], sum[305];//f[i][j]表示合并从i到j(包括i和j)所需要的费用
int dpmin()
{
int n = N/2;
memset(f,INF, sizeof(f));
for(int i = 0;i <= N;i++)
{
f[i][i] = 0;
}
for(int len = 2;len <= n;len++)
{
int j;
for(int i = 1;i+len-1 <= N;i++)
{
j = len+i-1;
for(int k = i;k < j;k++)
{
f[i][j] = min(f[i][j], f[i][k]+f[k+1][j] );
}
f[i][j] += sum[j]-sum[i-1];
//cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
}
}
int ans = INF;
for(int i = 1;i <= n;i++)
{
ans = min(ans, f[i][i+n-1]);
}
return ans;
}
int dpmax()
{
int n = N/2;
memset(f,0, sizeof(f));
for(int i = 0;i <= N;i++)
{
f[i][i] = 0;
}
for(int len = 2;len <= N/2;len++)
{
int j;
for(int i = 1;i+len-1 <= N;i++)
{
j = len+i-1;
for(int k = i;k < j;k++)
{
f[i][j] = max(f[i][j], f[i][k]+f[k+1][j]);
}
f[i][j] += sum[j]-sum[i-1];
//cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
}
}
int ans = 0;
for(int i = 1;i <= n;i++)
{
ans = max(ans, f[i][i+n-1]);
}
return ans;
}
int main()
{
int n;
scanf("%d",&n);
N = 2 * n;
//memset(f,INF, sizeof(f));
memset(sum,0,sizeof(sum));
for(int i = 1; i <= n;i++)
{
scanf("%d",&(s[i]));
f[i][i] = 0;
f[n+i][n+i] = 0;
sum[i] = sum[i-1]+s[i];
//cout << "sum[" << i << "]: " << sum[i] << endl;
}
for(int i = n+1;i <= N;i++)
{
sum[i] = sum[i-1]+s[i-n];
}
cout << dpmin() << endl;
cout << dpmax() << endl;
}