HDU 多校联合第五场

01

树形dp。。。比赛完看了看解题报告。发现我的状态写的太搓了,想到了先只考虑半数的,然后在一个全是A(或B)的块里面补上一个全的。当时没想着把这个也加到状态里去,写特判写的想哭。。。!

f[t][f][0] 表示t为根的子树,t被f攻击,并且算是f攻击的“连通块“里没有一个节点的攻击值是全值的最小值。

f[t][f][1] 表示t为根的子树,t被f攻击,并且算是f攻击的“连通块“里有一个节点的攻击值是全值的最小值。

sum = Σ{min(f[t->i][f][0], f[t->i][f^1][1])};
plus = min{ f[t->i][f][1] - min(f[t->i][f][0], f[t->i][f^1][1])}  //把某个值补全

f[t][f][0] = cost[t][f]/2 + sum;
f[t][f][1] = min(cost[t][f] + sum, cost[t][f]/2 + sum + plus);

 

View Code
using namespace std;

const int N = 220;

int dp[N][2][2];
int c[N][2];
int mp[N][N], n;
bool vis[N];

void dfs(int t, int f) {
    if(dp[t][f][0] != -1 && dp[t][f][1] != -1)    return ;

    int sum = 0, p = inf, i;
    for(i = 1; i <= n; ++i) {
        if(!vis[i] && mp[t][i]) {
            vis[i] = true;
            dfs(i, f); dfs(i, f^1);
            sum += min(dp[i][f][0], dp[i][f^1][1]);
            p = min(dp[i][f][1] - min(dp[i][f][0], dp[i][f^1][1]), p);

            ///det = min{dp[v][j][1] - min(dp[v][j][0], dp[v][1-j][1])};

            vis[i] = false;
        }
    }
    dp[t][f][0] = sum + c[t][f]/2;
    dp[t][f][1] = min(c[t][f] + sum, c[t][f]/2 + sum + p);
}

void init(int n) {
    for(int i = 0; i<= n; ++i) {
        for(int j = 0; j < 2; ++j) {
            for(int k = 0; k < 2; ++k)  dp[i][j][k] = -1;
        }
    }
    CL(vis, false);
}

int main() {
    //freopen("data.in", "r", stdin);

    int i, x, y, ans;
    while(~scanf("%d", &n)) {
        init(n);
        for(i = 1; i <= n; ++i) scanf("%d", &c[i][0]);
        for(i = 1; i <= n; ++i) scanf("%d", &c[i][1]);
        CL(mp, 0);
        for(i = 1; i < n; ++i) {
            scanf("%d%d", &x, &y);
            mp[x][y] = mp[y][x] = 1;
        }
        init(n); vis[1] = true;
        dfs(1, 0);
        ans = dp[1][0][1];
        //printf("%d ", ans);
        init(n); vis[1] = true;
        dfs(1, 1);
        //printf("%d\n", dp[1][1][1]);
        ans = min(ans, dp[1][1][1]);
        printf("%d\n", ans);
    }
    return 0;
}

 

03 01背包问题的变形

按斜率排序,因为01背包的过程是由上一层的状态推当前层的状态。所以可以把斜率相同的点放到一个背包里,按离原点的距离进行压缩,把前面所有点的状态压到当前点,然后决策这个点选还是不选。因为这些点放到同一层里进行决策,所以不会出现重复选择某个点的情况。

View Code
using namespace std;

const int N = 220;

int f[40010];

struct node {
    int x;
    int y;
    int t;
    int val;

    bool operator < (const node tmp) const {
        if(y*tmp.x == x*tmp.y)  return y < tmp.y;
        return y*tmp.x < x*tmp.y;
    }
}p[N];

int main() {
    //freopen("data.in", "r", stdin);

    int n, T, i, j, k;
    int t, v, l, cas = 0;
    while(~scanf("%d%d", &n, &T)) {
        for(i = 0; i < n; ++i) {
            scanf("%d%d%d%d", &p[i].x, &p[i].y, &p[i].t, &p[i].val);
        }
        sort(p, p + n);
        //for(i = 0; i < n; ++i)  printf("%d %d %d %d\n", p[i].x, p[i].y, p[i].t, p[i].val);

        CL(f, 0);
        for(i = 0; i < n; ++i) {
            k = i;
            while(p[i].y*p[k+1].x == p[k+1].y*p[i].x && k + 1 < n)   k++;

            for(j = T; j >= p[i].t; --j) {
                t = 0, v = 0;
                for(l = i; l <= k; ++l) {
                    t += p[l].t; v += p[l].val;    //压缩
                    if(j - t >= 0)   {
                        if(f[j-t] != -inf) {
                            f[j] = max(f[j], f[j-t] + v);
                        }

                    } else  break;
                }
            }
            i = k;
        }
        int ans = 0;
        for(i = 0; i <= T; ++i) ans = max(ans, f[i]);

        printf("Case %d: %d\n", ++cas, ans);
    }
    return 0;
}

 

04 完全是推规律

这个题M是我推出来的, 是gb推出来的。Orz

View Code
int main() {
    //freopen("data.in", "r", stdin);

    int t, n, m, x;
    LL k, i;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        if(n == 1)  {
            printf("2 2\n");
        } else if(n == 2) {
            printf("3 3\n");
        } else {
            m = 2; x = 2; i = 1; k = 1;
            while(1) {
                if(n - x <= 0)   break;
                n -= x; m += 1 + x;
                k += i*LL(x) + i + 1; x += 2;
                i++;
            }
            m += n - 1;
            k += i*LL(n);
            cout << m << " " << k << endl;
        }
    }
    return 0;
}

 

06

详见:Miller-Rabin + poLLard-rho 模板

 

07 

首先明白一点。每种方案的循环次数 = 各循环节大小的最小共倍数。

然后就可以枚举循环节的大小,要求这些循环节的和<= N,为什么可以<,因为其余的可以全部是1;

因为是要找最小共倍数的个数,所以如果这些数包含大于一个质因子,那么最小共倍数就是一定的,多加几个只会增加和,不会得到新的最小共倍数。

所以可以推出:枚举的所有循环节大小都是只包含一个质因子。然后递推或者记忆化搜索就可以了。

f[i][n]表示最大为n,当前枚举的素数是素数表中第i个素数

 

View Code
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#include <string>
#include <set>
#include <ctime>
#include <queue>
#include <map>
#include <sstream>

#define CL(arr, val)    memset(arr, (val), sizeof(arr))
#define REP(i, n)       for((i) = 0; (i) < (n); ++(i))
#define FOR(i, l, h)    for((i) = (l); (i) <= (h); ++(i))
#define FORD(i, h, l)   for((i) = (h); (i) >= (l); --(i))
#define L(x)    (x) << 1
#define R(x)    (x) << 1 | 1
#define MID(l, r)   ((l) + (r)) >> 1
#define Min(x, y)   (x) < (y) ? (x) : (y)
#define Max(x, y)   (x) < (y) ? (y) : (x)
#define E(x)    (1 << (x))
#define iabs(x)  ((x) > 0 ? (x) : -(x))

typedef long long LL;
const double eps = 1e-8;
//const int inf = ~0u>>2;

using namespace std;

const int N = 1024;

LL f[N][N];
int prime[N], cnt, lim;
bool isp[N];

void init() {
    CL(isp, true);
    CL(prime, 0);
    int i, j;
    for(i = 2; i < N; ++i) {
        for(j = i*i; j < N; j += i) isp[j] = false;
    }
    cnt = 0;
    for(i = 2; i < N; ++i) {
        if(isp[i])  {prime[cnt++] = i; }
    }
}

LL solve(int i, int n) {
    if(prime[i] > n)    return 1;

    if(f[i][n] != -1)   return f[i][n];

    f[i][n] = solve(i + 1, n);
    LL x = prime[i];
    while(x <= n) {
        f[i][n] += solve(i + 1, n - x);
        x *= prime[i];
    }
    return f[i][n];
}

int main() {
    freopen("data.in", "r", stdin);

    init();
    int n;
    while(~scanf("%d", &n)) {
        CL(f, 0xff);
        cout << solve(0, n) << endl;
    }
    return 0;
}

 

11

Lucas定理:

给出n, m, p。

把n, m写成p进制:

这里是二进制:

比如 n=1001101,m是从000000到1001101的枚举,我们知道在该定理中 C(0,1)=0, C(0, 0) = 0!/(0!*(0-0)!) = 1.

因此如果n=1001101的0对应位置的m二进制位为1那么C(n,m) % 2==0, 因此m对应n为0的位置只能填0,而1的位置填0,填1都是1(C(1,0)=C(1,1)=1);

不影响结果为奇数,并且保证不会出n的范围,因此所有的情况即是n中1位置对应m位置0,1的枚举,那么结果很明显就是:2^(n中1的个数)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2012-08-08 22:04  AC_Von  阅读(314)  评论(0编辑  收藏  举报