ccf备考

文章目录

  • 博客
  • 模板plus
    • 小根堆
    • 大根堆
    • 桶排序
    • 归并排序+逆序对
    • 区间最大频率
    • 交换相邻两数
    • 逆序数推排列数
    • kmp
  • 常用函数
        • 常用函数plus
          • 字符大小写转换
          • string查找替换
          • 字符串划分
          • 字符串转换为大写
          • 字符串转换为小写
  • 模板
    • 素数判断
    • 素数打表
    • 幂函数
    • 快速幂
    • 最大公约数___欧几里得算法(GCD)
    • 大数加法
    • 大数乘法
    • 小根堆
    • 大根堆
    • 桶排序
  • 动态规划
    • 最长公共子序列(长度+打印路径)
    • 最长递增子序列(长度+打印路径)
      • 只求长度的(java):
      • 求长度并打印的:
    • 最小生成树与最短路径
        • 说明
      • kruskal模板
      • prim模板
        • prim踩坑总结
      • SPFA算法
  • 动态规划
      • 割绳子
      • 切钢板
      • 嵌套矩形(DAG)
      • 最长上升子序列
      • 0-1背包问题
      • 完全背包
      • 多重背包
  • 石子问题

博客

模板plus

小根堆

#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;
}

kmp

  • 模板一:
#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);
}
  • 模板二:
    作者说明:
    s1为匹配串,s2为模式串。kmp算法中的next数组称为失配指针,表示s1[i]和s2[j]匹配失败时,最有效率的方法是让s1[i]和s2[j]中的哪个元素进行匹配。
    next数组有很多种定义方式,自己选了一种作为模板。
    比如: a b c d a b c
    i == : 0 1 2 3 4 5 6
    next:-1 0 0 0 0 1 2
    当匹配到i=6,出现失配时,next数组回溯到i=2的位置
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 堆栈容器
  • 字符大小写转换
  1. 利用algorithm库函数
#include //所需头文件
transform(str.begin(),str.end(),str.begin(),tolower); //将string转化为小写
transform(s.begin(), s.end(), s.begin(), toupper); //将string转化为大写
  1. 对字符加32或减32
  2. 如果用char数组,也可以自己手写两个转化为大写和小写方法, 此种方法用到了tolower(char c)和toupper(char c)两个方法:
#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;
}
  • string查找替换
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;
    }
  • str2int
#include 
int to_int(const string& a)
{
    int res;
    stringstream ss;
    ss << a;
    ss >> res;
    return res;
}
  • int2str
#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; 
}
  • 判断周几(已知1970.01.01为周四)
    思路:计算距1970.01.01多少天,然后计算
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);
}
  • string读入
char s[100];
cin.getline(s , 100); //read a line, discard \n
cin.get(s,100)//read a line, leave \n in queue  

72b8d205cdb65678b245e627199b4668.png

  • string转换为标准小写
#include//没有该头文件,iostream也可以
void toStandard(string &str)//注意:是对原string进行修改
{
    int len = str.size( );
    for(int i = 0;i <len;i++)
      str[i] = tolower( str[i] );
}
  • C/C++中sscanf()函数: 
      sscanf() - 从一个字符串中读进与指定格式相符的数据. 
      函数原型: 
      Int sscanf( string str, string fmt, mixed var1, mixed var2 …); 
      int scanf( const char format [,argument]… ); 
      说明: 
      sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。 
      其中的format可以是一个或多个 {%[
    ] [width] [{h | l | I64 | L}]type | ’ ’ | ‘\t’ | ‘\n’ | 非%符号} 
      注: 
      1、 * 亦可用于格式中, (即 %d 和 %s) 加了星号 () 表示跳过此数据不读入. (也就是不把此数据读入参数中) 
      2、{a|b|c}表示a,b,c中选一,[d],表示可以有d也可以没有d。 
      3、width表示读取宽度。 
      4、{h | l | I64 | L}:参数的size,通常h表示单字节size,I表示2字节 size,L表示4字节size(double例外),l64表示8字节size。 
      5、type :这就很多了,就是%s,%d之类。 
      6、特别的:%
    [width] [{h | l | I64 | L}]type 表示满足该条件的被过滤掉,不会向目标参数中写入值 。
      支持集合操作: 
      %[a-z] 表示匹配a到z中任意字符,贪婪性(尽可能多的匹配) 
      %[aB’] 匹配a、B、'中一员,贪婪性 
      %[^a] 匹配非a的任意字符,贪婪性
    例子:
  1. 常见用法。 
      char buf[512] = ; 
      sscanf(“123456 “, “%s”, buf); 
      printf(”%s\n”, buf); 
      结果为:123456
  2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。 
      sscanf(“123456 “, “%4s”, buf); 
      printf(”%s\n”, buf); 
      结果为:1234
  3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。 
      sscanf(“123456 abcdedf”, “%[^ ]”, buf); 
      printf("%s\n", buf); 
      结果为:123456
  4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。 
      sscanf(“123456abcdedfBCDEF”, “%[1-9a-z]”,buf); 
      printf("%s\n", buf); 
      结果为:123456abcdedf
  5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。 
      sscanf(“123456abcdedfBCDEF”, “%[^A-Z]”,buf); 
      printf("%s\n", buf); 
      结果为:123456abcdedf 
    6、给定一个字符串iios/12DDWDFF@122,获取 / 和 @ 之间的字符串,先将 “iios/“过滤掉,再将非’@'的一串内容送到buf中 
      sscanf(“iios/12DDWDFF@122”, “%*[/]/%[@]”,buf); 
      printf(”%s\n”, buf); 
      结果为:12DDWDFF 
    7、给定一个字符串““hello, world”,仅保留world。(注意:“,”之后有一空格) 
      sscanf(“hello, world”, “%*s%s”, buf); 
      printf("%s\n", buf); 
      结果为:world 
      %*s表示第一个匹配到的%s被过滤掉,即hello被过滤了 
      如果没有空格则结果为NULL。 
      sscanf的功能很类似于正则表达式, 但却没有正则表达式强大,所以如果对于比较复杂的字符串处理,建议使用正则表达式. 
      //------------------------------------------------------- 
      sscanf,表示从字符串中格式化输入 
      上面表示从str中,输入数字给x,就是32700 
      久以前,我以为c没有自己的split string函数,后来我发现了sscanf;一直以来,我以为sscanf只能以空格来界定字符串,现在我发现我错了。 
      sscanf是一个运行时函数,原形很简单: 
      int sscanf( 
      const char *buffer, 
      const char *format [, 
      argument ] … 
      ); 
      它强大的功能体现在对format的支持上。 
      我以前用它来分隔类似这样的字符串2006:03:18: 
      int a, b, c; 
      sscanf(“2006:03:18”, “%d:%d:%d”, a, b, c); 
      以及2006:03:18 - 2006:04:18: 
      char sztime1[16] = “”, sztime2[16] = “”; 
      sscanf(“2006:03:18 - 2006:04:18”, “%s - %s”,sztime1, sztime2); 
      但是后来,我需要处理2006:03:18-2006:04:18 
      仅仅是取消了‘-’两边的空格,却打破了%s对字符串的界定。 
      我需要重新设计一个函数来处理这样的情况?这并不复杂,但是,为了使所有的代码都有统一的风格,我需要改动很多地方,把已有的sscanf替换成我自己的分割函数。我以为我肯定需要这样做,并伴随着对sscanf的强烈不满而入睡;一觉醒来,发现其实不必。 
      format-type中有%[]这样的type field。如果读取的字符串,不是以空格来分隔的话,就可以使用%[]。 
      %[]类似于一个正则表达式。[a-z]表示读取a-z的所有字符,[^a-z]表示读取除a-z以外的所有字符。 
      所以那个问题也就迎刃而解了:

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, "%#0
X”, 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, "%0
x”, 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;
}
  • c/c++ 转换说明符:
  1. 通配符
          %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

常用函数plus

字符大小写转换
  1. 利用algorithm库函数
    注意tolower前要加双冒号
#include //所需头文件
transform(str.begin(),str.end(),str.begin(),::tolower); //将string转化为小写
transform(s.begin(), s.end(), s.begin(), ::toupper); //将string转化为大写
  1. 对字符加32或减32
  2. 如果用char数组,也可以自己手写两个转化为大写和小写方法, 此种方法用到了tolower(char c)和toupper(char c)两个方法:
#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;
}
string查找替换
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;
}

最大公约数___欧几里得算法(GCD)

#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;

最长递增子序列(长度+打印路径)

  • 问题描述:
    设L=是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=,其中k1
  • 第一种算法:转化为LCS问题求解
    设序列X=是对序列L=按递增排好序的序列。那么显然X与L的最长公共子序列即为L的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。
    最长公共子序列问题用动态规划的算法可解。设Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它们分别为L和X的子序列。令C[i,j]为Li与Xj的最长公共子序列的长度。
    这可以用时间复杂度为O(n2)的算法求解,由于这个算法上课时讲过,所以具体代码在此略去。求最长递增子序列的算法时间复杂度由排序所用的O(nlogn)的时间加上求LCS的O(n2)的时间,算法的最坏时间复杂度为O(nlogn)+O(n2)=O(n2)。
  • 第二种算法:动态规划
    设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:
    这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j

只求长度的(java):

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
b693872d68189eb15b1e6288bc90d853.png

kruskal模板

/*
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;
}

prim模板

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的模板

prim踩坑总结

  1. 对于普通的prim解法(第一个),当数据规模较大时将不能一次申请那么多内存空间
  2. 所以需要内存优化,可以用vector > g[maxN]来存放边集,但不能用map,时间复杂度太高,经测试只能得20分。
  3. 即便内存优化完也只能得70分,部分测试点超时,所以需要堆优化,满分通过。

SPFA算法

优化时每次取出队首结点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;
}

文章目录

  • 博客
  • 模板plus
    • 小根堆
    • 大根堆
    • 桶排序
    • 归并排序+逆序对
    • 区间最大频率
    • 交换相邻两数
    • 逆序数推排列数
    • kmp
  • 常用函数
        • 常用函数plus
          • 字符大小写转换
          • string查找替换
          • 字符串划分
          • 字符串转换为大写
          • 字符串转换为小写
  • 模板
    • 素数判断
    • 素数打表
    • 幂函数
    • 快速幂
    • 最大公约数___欧几里得算法(GCD)
    • 大数加法
    • 大数乘法
    • 小根堆
    • 大根堆
    • 桶排序
  • 动态规划
    • 最长公共子序列(长度+打印路径)
    • 最长递增子序列(长度+打印路径)
      • 只求长度的(java):
      • 求长度并打印的:
    • 最小生成树与最短路径
        • 说明
      • kruskal模板
      • prim模板
        • prim踩坑总结
      • SPFA算法
  • 动态规划
      • 割绳子
      • 切钢板
      • 嵌套矩形(DAG)
      • 最长上升子序列
      • 0-1背包问题
      • 完全背包
      • 多重背包
  • 石子问题

动态规划

割绳子

题目
给定一根长度为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。
思路:算法:动态规划、贪婪算法
动态规划:

  • 定义函数f(n)表示为把长度为n的绳子剪成若干段后各段长度乘积的最大值。
  • 对于第一刀,我们有n-1种可能的选择,可推导出f(n)=max{f(i)*f(n-i)};
  • 很明显这是一个从上至下的递归,但是这个递归存在很多重复的计算,所以使用至下而上的动态规划,将子问题的最优解保存。
  • 注意绳子剪成ix(n-i)和(n-i)xi是相同的;
  • 注意不符合切割条件的输入n,以及输入为2、3长度时的结果,因为题中规定m>1。

贪心:

  • 贪心算法在对问题求解时,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解;
  • 选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关;
  • 题目贪婪策略:当n>=5时,尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。

代码:

/*
方法一: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)

有向无环图(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;
}

最长上升子序列

40bffffc58f1037bb88ce9c96c6cfee9.jpeg
7320c3a764843fee1403d3881d8aba71.jpeg
28ed6a7d3c29e11fd30b3e9f902afbfc.jpeg
de77702e39e2e8a973c9719938420aa6.jpeg

/**
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;
}

0-1背包问题

给定 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]数组。
040bb118e8d53e743f163465c30378fd.png
(第一行和第一列为序号,其数值为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堆石子的总数量。那么就有状态转移公式:
3b4749cf90438cfec7f3551c3ff2a1b9.png

  1. 区间DP
  2. 记忆化DP

变形: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;
}

你可能感兴趣的:(ccf)