zoukankan      html  css  js  c++  java
  • 2013512 训练赛后总结

     感谢 谢大,duoxida,zhsl ,教会了我几个训练赛当中没有想出来的题。

    A Force Brute

    题意:  没发现其水题本质啊..   给定 N个单词, 然后问最大循环次数. 

    解法:   KMP  next数组的运用, 更详细的可以看这一篇总结 http://www.cnblogs.com/yefeng1627/archive/2013/04/28/3050027.html

    这里就简要说下,  因为求next的过程是一个一个构造循环节的, (L+1)-next[L+1] 即为当前字符串的最小循环节长度, 而 L%( (L+1)-next[L+1] ) 表示目前 循环节构造了多少个,若为0则意味着构造满了一个循环.  而 L / ( (L+1) - next[L+1] )  表是目前已经构造了的循环节数量.  所以最终结果为   

      令 d = (L+1) - next[L+1] ,   则 ans  = L/d + (L%d != 0) 

    若 next[L+1] = 1, 则代表整个串是 最小循环节. 其实发现可以不特殊处理的.

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<map>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    const int N = (int)1e5+10;
    
    char str[N*25], s[30];
    int nxt[N], a[N], top, n;
    
    map<string,int> mp;
    int main(){
        int T;
        scanf("%d", &T); getchar();
        while( T-- ){
            gets(str);
            top = n = 0;
            mp.clear();    
            char *p = strtok( str, " " );
            while( p ){
                if( mp.count(p) == 0 )    mp[p] = ++top;    
                a[n++] = mp[p];
                p = strtok( NULL, " " );
            }
        //    for(int i = 0; i < n; i++)
        //        printf("%d ", a[i] ); puts("");
            int i = 0, j = 1; nxt[1] = 0;
            while( j <= n ){
                if( i == 0 || a[i-1] == a[j-1] )
                    nxt[++j] = ++i;
                else    i = nxt[i];
            }
        //    for(int i = 1; i <= n+1; i++)
        //        printf("%d ", nxt[i] ); puts("");
            if( nxt[n+1] != 1 ){
                int d = (n+1)-nxt[n+1];
                printf("%d\n", n/d + (n%d!=0) );
            }     
            else puts("1");    
        }
        return 0;
    }
    View Code

    B God Lin’s Harem

    题意:  N个点,每个点有个权值, 操作主要是,修改单点,删除单点,查询单点,查询最值下标,相同则输出最近的。

    解法: 线段树即可, 初始便建立N个顶点,因为最多N次操作。 然后对于N操作,每次给其一个优先级,这样在查询时,

    出现权值相同,则取优先级大的。 对于更新和修改操作。单点更新直接到底就好。查询操作O(1).

    顺便说下,更新操作是不影响优先级的。别像我一样,在这里WA好久。

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<string>
    #include<algorithm>
    #include<queue>
    #include<map>
    using namespace std;
    typedef long long LL;
    
    #define lch rt<<1,l,m
    #define rch rt<<1|1,m+1,r
    const int N = (int)1e5+100;
    
    LL tree[N<<2];
    int idx[N<<2], cnt[N];
    int n, top;
    
    void push_up(int rt){
        int lc = rt<<1, rc = rt<<1|1;
        if( (tree[lc]>tree[rc]) || ((tree[lc]==tree[rc])&&(cnt[idx[lc]]>cnt[idx[rc]]) ) )
            tree[rt] = tree[lc], idx[rt] = idx[lc];
        else tree[rt] = tree[rc], idx[rt] = idx[rc];
    }
    void build(int rt,int l,int r){
        tree[rt] = -1; idx[rt] = l;
        if( l == r ){ return; }
        int m = (l+r)>>1;
        build( lch ), build( rch );
        push_up( rt );
    }
    void update(int rt,int l,int r,int a,LL b, bool flag){
        if( l == r ){
            if(flag) tree[rt] = b;
            else tree[rt] += b;
            return;    
        }
        int m = (l+r)>>1;
        if(a <= m) update( lch, a, b,flag );
        else update( rch, a, b,flag );
        push_up(rt);
    }
    int main(){
        freopen("1.in","r",stdin);    
        while( scanf("%d", &n) != EOF ){
            if( n == 0 ) break;    
            char op[2];
            int x, y, k = 1;
            bool flag = true;
            top = 0;    
            memset( cnt, 0, sizeof(cnt));    
            
            build( 1, 1, n  ); //print();
            
            for(int i = 0; i < n; i++){
                scanf("%s", op);
                switch( op[0] ){
                    case 'N': 
                        scanf("%d",&x); cnt[k] = ++top;
                        update(1,1,n,k++,x,true);
                        break;
                    case 'I': 
                        scanf("%d%d",&x,&y);
                        if( cnt[x] == -1 ) continue;    
                        //cnt[x] = ++top;
                        update(1,1,n,x,y,false); break;
                    case 'D': 
                        scanf("%d%d",&x,&y); 
                        if( cnt[x] == -1 ) continue;    
                        //cnt[x] = 0;    
                        //    cnt[x] = ++top;
                        update(1,1,n,x,-y,false); break;
                    case 'E':
                        scanf("%d", &x); cnt[x] = -1;
                        update(1,1,n,x,-1,true); break;
                    case 'S':
                        if( flag ) flag = false;
                        else printf(" ");
                        printf("%d", idx[1] );
                } 
            }    
            puts("");    
        }
        return 0;
    }

    C Harry Potter

    题意: 一个人从0点出发,走到X,每次只能走X+1, 或者X*2. 问最小时间。X <= 10^18.

    解法: X小可以用BFS搜索, 但是这里其实可以不用,基于贪心的原则,走最大理论上是最优,但若从前面走则不一定。

    那么我们从X走到0,每次走 X/2 ,若X是奇数则 X-1, 这样即为最终结果。

    View Code
    #include<cstdio>
    typedef long long LL;
    
    int main(){
        int T;
        scanf("%d", &T);
        while( T-- ){
            LL x, t = 0;
            scanf("%lld", &x);
            while( x ){
                if( x&1 ) x--;
                else x /= 2;
                t++;    
            }
            printf("%lld\n", t );    
        }
        return 0;
    }

    顺便贴个BFS版本,不过这里A不了的

    View Code
    #include<cstdio>
    #include<map>
    #include<queue>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    
    const LL inf = (LL)1e18+10;
    
    
    struct node{
        LL x, t;
    }pre,nxt;
    
    queue<node> Q;
    map<LL,int> mp;
    
    LL solve(LL X){
        while( !Q.empty() ) Q.pop();
        mp.clear();
        pre.x = 0, pre.t = 0;
        mp[ pre.x ] = 1;
        Q.push( pre );
        while( !Q.empty() ){
            pre = Q.front(); Q.pop();
            if( pre.x == X ) return pre.t;
            if( pre.x+1 < inf ){
                if( mp.count(pre.x+1) == 0 ){    
                    if(pre.x+1==X) return pre.t+1;        
                    nxt.x = pre.x+1; nxt.t = pre.t+1;
                    Q.push( nxt );
                    mp[ nxt.x ] = 1;    
                }    
            }
            if( pre.x*2 < inf ){
                if( mp.count(pre.x*2) == 0 ){
                    if( pre.x*2 == X ) return pre.t+1;
                    nxt.x = pre.x*2; nxt.t = pre.t+1;
                    Q.push( nxt );
                    mp[ nxt.x ] = 1;
                }    
            }    
        }
    }
    int main(){
        int T;
        scanf("%d", &T);
        while( T-- ) {
            LL K;    
            scanf("%lld", &K); 
            printf("%lld\n", solve(K) );    
        }
        return 0;
    }

    D  (LCS)^2

    题意: 题目很直白啊,两个长度为10^6的串,求最长公共子序列,输出。

    解法: 表示不会, O(NlogN)的算法

    E N皇后问题

    题意:  没看明白

    F 查询成绩

    题意:  给两个串, s,t , 其中t中部分为字符'*', 问t是否为S的片段。strlen( s, t ) <= 110

    解法:  递归匹配,即可。 若  Tj = '*', 则Tj+1 与 Si开始匹配,  若Tj != '*' ,则比较 Tj ,Si.  注意多个*在一起时,只算一个*

    View Code
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    char s[150], t[150];
    int la, lb;
    int find( int x, int y ){
        if( (y<lb)&&(x>=la) ) return 0;
        if( y == lb ) return 1;    
        if( t[y] == '*' ){    
            while( t[y+1] == '*' ) y++;    
            int tmp;    
            for(int i = x; i < la; i++){
                if( s[i] == t[y+1] ){ 
                    tmp = find( i+1, y+2 );                    
                    if(tmp) return 1;    
                }    
            }
            return 0;    
        }    
        else{
            if( s[x] != t[y] ) return 0;
            else return find( x+1, y+1 );
        }
    }
    
    int main(){
        while( scanf("%s %s", s, t+1 ) != EOF){
            t[0] = '*';    
            la = strlen(s), lb = strlen(t);    
            puts( find(0,0) ? "yes" : "no" );    
        }
        return 0;
    }

    G 卡片重组

    题意:  N张带有权值的卡片,顺序摆放,先要求分成多份,并且每份单调,不能交换位置,单分中权值为sum(i,j)*num(i,j) 。

    解法:  动态规划  

      dp( i ) 表示前 i 张卡片最大权值,转移方程为

      dp( i ) = Max{  dp(j ) + Sum(j+1,i)* Num( j+1,i)  }   其中  j < i,  且 ( j+1, i ) 区间单调

    View Code
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    typedef long long LL;
    const int N = 1010;
    
    int dp[N], val[N], sum[N];
    int n;
    
    int main(){
        while( scanf("%d", &n) != EOF){
            for(int i = 1; i <= n; i++)
                scanf("%d", &val[i] );
            sum[0] = 0;
            for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+val[i];
            memset( dp, 0, sizeof(dp));
            for(int i = 1; i <= n; i++){
                dp[i] = dp[i-1] + val[i];
                int j = i-2; 
                while( (j>=0) && (val[j+1]>val[j+2]) )
                    dp[i] = max( dp[i], dp[j] + (i-j)*(sum[i]-sum[j]) ), j--;
                j = i-2;
                while( (j>=0) && (val[j+1]<val[j+2]) )
                    dp[i] = max( dp[i], dp[j] + (i-j)*(sum[i]-sum[j]) ), j--;
            }
            printf("%d\n", dp[n] );    
        }    
        return 0;
    }

    H 括号匹配

    题意: 给一括号串 ( ?? ) , 其中 ? 可为 ( 或者 ), 问合法方案数

    解法: 动态规划

      因为括号匹配则,将 左括号 "(" 看作1, 右括号")"看作-1,则合法的括号串其所有前缀和都 >= 0.

      又因为串总长为 1000, 则其前缀和最大为 500. 

      状态方程 dp ( i, j )  表示 前i个括号,最后一个前缀和为 j的 方案数 (之前的都满足>=0)

      转移方程为:

        Si = '(' ,  则前缀和+1,  dp( i, j+1 ) += dp( i-1, j ) ,   其中 j+1 <= 500

        Si = ')',   则前缀和-1,     dp( i, j-1 )  += dp( i-1,j ),     其中 j-1 >= 0 

        Si = '?',   则前缀和可能+1,也可-1, 同样要满足上面 j+1 <= 500, j-1 >= 0

            dp( i, j+1 ) += dp( i-1, j )

            dp( i, j-1 ) += dp( i-1, j )

    View Code
    #include<cstdlib>
    #include<cstdio>
    #include<cstring>
    
    typedef long long LL;
    const int Mod = (int)1e9+7;
    
    const int N = 1010;
    const int M = 500;
    LL dp[N][1200];
    
    char s[N];
    
    int main(){
        while( scanf("%s", s+1) != EOF){
            int n = strlen(s+1);    
            memset(dp,0,sizeof(dp));
            dp[0][0+M] = 1;
            for(int i = 1; i <= n; i++){
                for(int j = 0; j <= 500; j++){
                    if( (s[i] == '(')&&(j+1<=500) )    
                        dp[i][ (j+1)+M ] = (dp[i][(j+1)+M]+dp[i-1][j+M])%Mod;
                    else if( (s[i] == ')') && (j-1>=0) )
                        dp[i][ (j-1)+M ] = (dp[i][(j-1)+M]+dp[i-1][j+M])%Mod;
                    else if( s[i] == '?') {
                        if( j+1 <= 500 )    
                            dp[i][ (j+1)+M ] = (dp[i][(j+1)+M]+dp[i-1][j+M])%Mod;
                        if( j-1 >= 0 )    
                            dp[i][ (j-1)+M ] = (dp[i][(j-1)+M]+dp[i-1][j+M])%Mod;
                    }
                }    
            }    
            printf("%lld\n", dp[n][M] );    
        }    
        return 0;
    }

    I 排列的逆序数

    题意:  {1,2...n}的所有排列中逆序数为k的排列个数。

    解法:  动态规划

        状态方程 dp( i, j )  表示前 i个数,逆序数为 j 的排列数,  因为最大逆序数为  n*(n-1)/2,  当n = 100, 则lim 接近5000

        转移方程  dp( i, j+k  ) +=  dp( i-1, j ) , 其中  k表示第i位贡献的逆序数, 取值范围为  [0, n-i], 并且 前i个数的最大逆序数量为  n*i - i*(i+1)/2,

    所以 j <= n*i - i*(i+1)/2. 

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    const int mod = (int)1e9+7;
    
    typedef long long LL;
    
    LL dp[110][5010];
    
    int n, K;
    
    int main(){
        while( scanf("%d%d", &n,&K) != EOF){    
            int lim = n*(n-1)/2;    
            memset( dp, 0, sizeof(dp));
            dp[0][0] = 1;    
            for(int i = 1; i <= n; i++){    
                int cnt = i*n-(1+i)*i/2;    
                for(int j = 0; j <= cnt; j++){    
                    if( dp[i-1][j] == 0 ) continue;    
                    for(int k = 0; k <= n-i; k++)
                        if( j+k <= lim )
                            dp[i][j+k] = (dp[i][j+k]+dp[i-1][j])%mod;
                }
            }
            printf("%lld\n", dp[n][K] );    
        }
        return 0;
    }

    J 物品选择

    题意: N件物品,都有个价值,部分是主件,部分是附件,附件需要在主件被选情况下才可选择。

    解法: 背包

      首先分组, 假定有 m个主件, 每个主件有 ni个附件。

      令 g( i, j ) 表示 从第i个主件中,取 j个物品的最大价值 , 其中  j <= ni,   g(i,0)看作只去主件本身 

      则令 dp( i , j ) 表示前 i个主件 ,取J个物品的最大价值, 转移方程为:

        dp( i , j ) = Max{ dp(i-1, j),  dp( i-1, j-(k+1) ) + g(i, k) }   //其中 j - (k+1) >= 0,  k <= mi 

      然后 g( i, j ) 求法也差不多。

    View Code
    #include<cstdio>
    #include<cstdlib>
    #include<vector>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    
    const int N = 1010;
    
    vector<int> Q[1010];
    
    int g[N][N], d[N][N], dp[N][N], a[N][N];
    int n, K;
    int val[N], v[N], L[N];
    bool flag[N];
    int sum[N];
    
    int main(){
        while( scanf("%d%d", &n,&K) != EOF){
            memset(flag,0,sizeof(flag));
            for(int i = 0; i <= n; i++) Q[i].clear();
            for(int i = 1; i <= n; i++){
                int p;    
                scanf("%d%d",&val[i],&p);
                if( p != i ) Q[p].push_back(val[i]);
                flag[p] = true;    
            }    
            memset(g,0,sizeof(g));
            for(int x = 1; x <= n; x++){ // 第x件物品,其为主件
                int len = (int)Q[x].size();    
                if( flag[x] ){
                    // 0,1 pack    
                    memset(d,0,sizeof(d));    
                    d[0][0] = val[x];    
                    for(int i = 1; i <= len; i++){
                        for(int j = 0; j <= i; j++){
                            if( j == 0 ) d[i][j] = d[i-1][j];        
                            else if( j == i ) d[i][j] = d[i-1][j-1] + Q[x][i-1];    
                            else d[i][j] = max( d[i-1][j], d[i-1][j-1]+ Q[x][i-1] );    
                        }    
                    }    
                    for(int i = 0; i <= len; i++)
                        g[x][i] = d[len][i];
                }    
            }
            int cnt = 0;
            for(int i = 1; i <= n; i++){
                if( flag[i] ){
                    L[++cnt] = Q[i].size();
                    for(int j = 0; j <= L[cnt]; j++)
                        a[cnt][j] = g[i][j];
                }    
            }
            memset( dp,0, sizeof(dp));    
            for(int i = 1; i <= cnt; i++){
                for(int j = 0; j <= K; j++){
                    dp[i][j] = dp[i-1][j];
                    if( j > 0 ){    
                        for(int k = 0; (k<=L[i]) && (k<j); k++){
                            dp[i][j] = max( dp[i][j], dp[i-1][j-(k+1)] + a[i][k] );    
                        }    
                    }    
                }    
            }    
            printf("%d\n", dp[cnt][K] );    
        }    
        return 0;
    }
  • 相关阅读:
    C#多线程编程之:集合类中Synchronized方法与SyncRoot属性原理分析
    Newtonsoft.Json 序列化和反序列化 以及时间格式 2 高级使用
    C++:基类和派生类
    C++:友元(非成员友元函数、成员友元函数、友元类)
    C++:静态成员
    C++:向函数传递对象(对象、对象指针、对象引用)
    C++:常类型Const
    C++:对象的赋值和复制
    C++:类的组合
    C++:析构函数
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/3074506.html
Copyright © 2011-2022 走看看