UESTC 2014 Summer Training #7 Div.2

  DAY7一开始状态还行,最高纪录rank7,然后没力气了,一直跌到rank24,问题还是很多呐。昨天总结的问题依然很大。

Problem A  UVA 12377

  就一开始还是慌了,没审清楚题意就去WA了一发。

  大意是给你一个数字表示的字符串,最高20位,第一表示有N个质数,后面是其幂的非降序排列,所求能表示的数个数。

  简单的排列组合题,不过需要处理出2~END的不同划分,并且满足非降序,用dfs处理即可。

  具体就是dfs过程中记录下每一个数字出现的次数,因为相同的次数是不计顺序的,需要在统计时除以cnt!(阶乘)

  对于每一种划分,增加的方案数 = 9!/(9-N+1)!/cnt[i]!

  我的做法是dfs字符串s,上一个数x, 当前划分出的个数n,每次按照剩余的长度枚举当前划分出的数字,并且满足不小于上一个数x,满足的话,

  还需要纪录下其出现的次数,最后dfs的串为空的时候检查划分出的个数,满足开始统计

  

  下午在做的时候,也出现了很多小问题:

  1.划分串长达19位,如果个数为1,2之类,转化为整数就可能会爆炸,需要用long long,一组数据 211...(19个1)

  2.fgets整行读入,字符串末尾带上了 ,需要自己处理掉,不然strlen返回值不是理想结果

  3.!!!!!!!!!!!!!!!!中间可能会出现0,而0不能作为一个数的前导,包括0,所以如果dfs枚举出字符串的前导为0时,中断(<---下午没A的原因

  这道题自己的问题还是很大的,一开始的审题不仔细,对数据考察的不深入,爆int,以及最后默认没有0,都是很难解决的"小毛病",却是很致命的.

  

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 25;
const int A[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800};

int T, tot;
long long numcnt[maxn], hashnum[maxn];
long long ans;
char code[maxn];

void dfs(char *s, long long x, int num, int upper)
{
    if(*s == 0 && upper == num) {
        long long cnt = 1;
        for(int i = 0; i < upper; i++)
            cnt *= (9-i);
        for(int i = 0; i < tot; i++)
            cnt /= A[numcnt[i]];
//        cout << tot << endl;
//        cout << "cnt" <<  cnt << endl;
        ans += cnt;
    }
    int len = (int)strlen(s);
//    cout << x << ' ' << len << endl;
    for(int i = 1; i <= len; i++) {
        long long _last;
        char tmp[maxn];
        strncpy(tmp, s, sizeof(char)*i);
        tmp[i] = 0;
        sscanf(tmp, "%lld", &_last);
        if(_last < x || tmp[0] == '0')    continue;
        int pos;
        bool has = false;
        for(int j = 0; j < tot; j++)
            if(hashnum[j] == _last) {
                has = true;
                numcnt[j]++;
                pos = j;
                break;
            }
        if(!has) {
            hashnum[tot] = _last;
            pos = tot++;
            numcnt[pos] = 1;
        }    
        dfs(s+i, _last, num+1, upper);
        numcnt[pos]--;
        if(!has)    tot--;
    }
}

int main()
{
#ifdef LOCAL
    freopen("A.in", "r", stdin);
#endif
    scanf("%d", &T);
    getchar();
    while(T--) {
        fgets(code, maxn, stdin);
        int len = strlen(code);
        while(code[len-1] < '0' || code[len-1] > '9') {
            code[len-1] = 0;
            len--;
        }
        tot = 0;
        ans = 0;
        memset(hashnum, 0, sizeof(hashnum));
        memset(numcnt, 0, sizeof(numcnt));
        dfs(code+1, 0, 0, code[0]-'0');
//        cout << cnt << endl;
        printf("%lld
", ans);
    }
    return 0;
}

Problem F  UVA 12382

  对于一个M*N的01矩阵,给出每行每列要求出现1的最少个数,球要满足条件填入1的最少个数。

  

  贪心的原理还是不是很理解,以下废话只是个人的一些思考

(废话->)由于交叉点的1可以同时满足行列,就看怎么让交叉点最多,比如以填行同时使交叉点最多,可以对行需求排序,从高到低放置1,放置第i行的时候,为了使得

下一行,能够放置的列数更多(交叉情况)(避免列不够而这行没有满足需要添加不交叉点),如果列放置需求(列数)已经小于这行需求,就在这行其他位置再填入一些1

最后可能有些列没满足,加上即可

  由于需要取出需求最大的列,用优先队列维护,注意是k列都取出后,再减一放进去,而不是边pop边push,小于等于0的列就不用push了

/**Updated**/

用模拟的方法去实现,我们肯定要将每行都至少放置需要的个数满足,即每行放置需求个数的1,现在考虑这些1放在哪些位置,贪心的策略是,选择需求最多的列来放置,这样可以保证在做下一行的时候,能够选择的列更多(举个列子,列需求1 2,行需求1 2 ...,如果第一行放置1 0,第二行就只能放置为0 1,再去放上1个不交叉的,这种策略就不是最优的:保证尽量多的交叉点)

/**Updated**/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 1000+50;

int T, m, n, ans;
priority_queue<int, vector<int>, less<int> > q;

int main()
{
#ifdef LOCAL
    freopen("I.in", "r", stdin);
#endif
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &m, &n);
        for(int i = 0; i < m; i++) {
            int tmp;
            scanf("%d", &tmp);
            q.push(tmp);
        }
//        cout << "top " << q.top() << endl;
        ans = 0;
        for(int i = 0; i < n; i++) {
            int col;
            scanf("%d", &col);
            int tot = 0;
            int tofill[maxn];
            for(int j = 0; j < col; j++) 
                if(!q.empty()) {
                    tofill[tot++] = q.top();    q.pop();
                }
            for(int j = 0; j < tot; j++)
                if(tofill[j] > 1)    q.push(tofill[j]-1);
            ans += col;
//            if(T == 1)    cout << ans << endl;
        }
        while(!q.empty())    {
//            if(T == 2)    cout << "top " << q.top() << endl;
            ans += q.top();
            q.pop();
        }
        printf("%d
", ans);
    }
    return 0;
}

Problem H  UVA 12384

   先预处理出素数,然后扫一遍,每个数都往前查找,寻找第一个大于x的数的位置,由于s[i]表示以a[i]为尾的连续非降序列个数,所以如果x>=a[i],指针可以直接跳s[i]个单元,

当然这种方法算比较渣的。。吗?

  还有一种思路是:单调队列。。。我没听懂。。。

  再一种思路:线段树!

  我们可以把a[i]作为点的坐标,i作为该点的值p[a[i]] = i,计算s[i]的时候,需要往前寻找第一个a[j]>a[i],即!查找a[j]<a[i]的最大的j!,可以转化为线段树查找最大值

线段1~a[i]-1的最大值就是要找的j...

  理解了好久T T

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cstring>

using namespace std;

const int maxn = 100000+50;

int T, n, m, tot;
int prime[maxn*10*5], x[maxn], s[maxn];
long long sum;

bool isprime(int x)
{
    if(x == 2)    return true;
    if(x % 2 == 0)    return false;
    for(int i = 3, t = sqrt(x); i <= t; i += 2)
        if(x % i == 0)    return false;
    return true;
}

int main()
{
#ifdef LOCAL
    freopen("H.in", "r", stdin);
#endif
    scanf("%d", &T);
    for(int i = 2, t = maxn*2*10; i < t; i++)
        if(isprime(i))    prime[++tot] = i;
//    cout << tot << endl;
    while(T--) {
        scanf("%d%d", &n, &m);
        sum = 0;
        memset(s, 0, sizeof(s));
        for(int i = 1; i <= n; i++) {
            if(i == 1)    s[i] = 1;
            else if(prime[i] %m >= prime[i-1] %m) {
                int j = i-s[i-1];    s[i] = s[i-1];
                while(j >= 1 && prime[i] % m >= prime[j] % m) {
                    s[i] += s[j];
                    j -= s[j];
                }
/*
                s[i] = s[i-1];
                int j = i-s[i-1]-1, cnt = 1;
                while(j >= 1 && prime[i] % m >= prime[j] % m) {
                    cnt++;
                    j--;
                }    
                s[i] += cnt; 
*/
            }
            else    s[i] = 1;
            sum += s[i];
            //cout << s[i];
        }
        //cout << sum << endl;    
        printf("%lld
", sum % m);    
    }
    return 0;
}

 顺带练习一发筛法,后面那个break不懂哟

void getPrime()
{
    int maxm = maxn*40;
    for(int i = 2; i <= maxm; i++) {
        if(!flag[i])    prime[++tot] = i;
        for(int j = 1; j <= tot && i*prime[j] <= maxm; j++) {
            flag[i*prime[j]] = true;
            if(i % prime[j] == 0)    break;
        }
    }
}

 Problem I  UVA 12385

  正规做法 DP

  容易注意到需要考虑每一个字符上一次出现的位置,记为last[c[i]],显然a-a-a比a---a更优

  f[c]表示以c为尾巴的最长序列  f[c] = max(f[c-1], f[last[c]]+1), last[c]不存在的话,f[c] = 0,看了半天题解才懂。。懒得写了。。。

  傻叉瞎搞法:

  扫一遍把所有的线段跑出来,当然不包括a-a-a这种,就a-a的,然后sort下贪心。。。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 100000+50;

struct interval{
    int x1, x2;

    void set(int x1, int x2) {
        this->x1 = x1;    this->x2 = x2;
    }

}seg[maxn];

int T, n, tot;
int lastn[maxn];

int cmp(const interval &a, const interval &b)
{
    if(a.x2 == b.x2)
        return a.x1 > b.x1;
    else    return a.x2 > b.x2;
}

int main()
{
#ifdef LOCAL
    freopen("I.in", "r", stdin);
#endif
    scanf("%d", &T);
    while(T--) {
        memset(lastn, -1, sizeof(lastn));
        tot = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            if(lastn[x] != -1) {
//                cout << x << ' ' << lastn[x] << ' ' << i << endl;
                seg[tot].set(lastn[x], i);
                tot++;
            }
            lastn[x] = i;
        }
        sort(seg, seg+tot, cmp);
        int left = maxn*2;
        long long ans = 0;
        for(int i  = 0; i < tot; i++) {
            if(seg[i].x2 <= left) {
                ans++;
                left = seg[i].x1;
            }
            else if(seg[i]. x1 > left) left = seg[i].x1;    
        }
        printf("%lld
", ans);
    }
    return 0;
}