2012 Multi-University #10

容斥原理 A Number Sequence

题意:给出n个数,b1,b2,b3……bn,构造n个数,a1,a2,……an(ai>1),使得a1*a2*a3……an=b1*b2……bn

分析:容易想到的是将bi分解质因数,然后记录每个质因数的个数。那么题目变成:对于(每个质因数个数为m个划分到n个不同的容器的方案数),注意ai>1,所以没有某个数没有质因数。记f(n)为n个数字可能有1的方案数,g(n)为n个数字一定没有1的方案数。则,得到听说这是二项式反演

#include <bits/stdc++.h>

typedef long long ll;
const int MOD = 1e9 + 7;
int b[25];
int comb[505][505];
std::vector<int> primes;
int cnt[505];
int n, tot;

void init_binom() {
    for (int i=0; i<=500; ++i) {
        comb[i][0] = comb[i][i] = 1;
        for (int j=1; j<i; ++j) {
            comb[i][j] = (comb[i-1][j] + comb[i-1][j-1]) % MOD;
        }
    }
}

void factorize(int n) {
    for (int i=2; i*i<=n; ++i) {
        while (n % i == 0) {
            n /= i;
            primes.push_back (i);
        }
    }
    if (n > 1) {
        primes.push_back (n);
    }
}

int get(int m, int n) {
    return comb[m+n-1][n-1];
}

int solve() {
    int ret = 1;
    for (int i=0; i<tot; ++i) {
        ret = (ll) ret * get (cnt[i], n) % MOD;
    }
    for (int i=1; i<n; ++i) {
        int tmp = 1;
        for (int j=0; j<tot; ++j) {
            tmp = (ll) tmp * get (cnt[j], n - i) % MOD;
        }
        tmp = (ll) tmp * comb[n][i] % MOD;
        if (i & 1) {
            ret = ((ret - tmp) % MOD + MOD) % MOD;
        } else {
            ret = (ret + tmp) % MOD;
        }
    }
    return ret;
}

int main() {
    init_binom ();
    while (scanf ("%d", &n) == 1) {
        primes.clear ();
        for (int i=1; i<=n; ++i) {
            scanf ("%d", b+i);
            factorize (b[i]);
        }
        std::sort (primes.begin (), primes.end ());
        memset (cnt, 0, sizeof (cnt));
        tot = 0;
        if (!primes.empty ()) {
            cnt[tot++] = 1;
        }
        for (int i=1; i<primes.size (); ++i) {
            if (primes[i] != primes[i-1]) {
                cnt[tot++] = 1;
            } else {
                cnt[tot-1]++;
            }
        }
        printf ("%d
", solve ());
    }
    return 0;
}

分块哈希 B Paint The Wall

题意:两种操作:1.将[l, r]区间的颜色涂成次color,2.询问[l, r]区间上color颜色的数量.(color<2^31)

分析:将n长度分块成sqrt(n)块,那么[ql, qr]的操作映射到[ql / block, qr / block]块上操作,对于(ql+block+1, qr/block)而言整块都是同一颜色,加上lazy标记,复杂度O(1),至于两端需要暴力操作,总的一次操作的复杂度为sqrt (n).还有线段树+剪枝优化(离线,颜色最大最小)也可以过,数据水.

#include <bits/stdc++.h>

const int N = 1e5 + 5;
struct Hash_Block {
    int size, col;
    std::map<int, int> cnt;
}hash[350];
int c[N];
int n, m, block, tot;

void init_hash() {
    block = (int) sqrt (n + 0.5);
    tot = (n - 1) / block + 1;
    for (int i=0; i<tot; ++i) {
        hash[i].size = std::min (n, (i + 1) * block) - i * block;
        hash[i].col = -1;
        hash[i].cnt.clear ();
    }
    for (int i=0; i<n; ++i) {
        hash[i/block].cnt[c[i]]++;
    }
}

void push_down(int b) {
    if (hash[b].col != -1) {
        hash[b].cnt.clear ();
        int R = std::min ((b+1) * block, n);
        for (int i=b*block; i<R; ++i) {
            c[i] = hash[b].col;
            hash[b].cnt[c[i]]++;
        }
        hash[b].col = -1;
    }
}

void updata(int ql, int qr, int color) {
    int lb = ql / block, rb = qr / block;
    for (int i=lb+1; i<rb; ++i) {
        hash[i].col = color;
    }
    if (lb != rb) {
        push_down (lb);
        push_down (rb);
        for (int i=ql; i<(lb+1)*block; ++i) {
            hash[lb].cnt[c[i]]--;
            c[i] = color; hash[lb].cnt[color]++;
        }
        for (int i=rb*block; i<=qr; ++i) {
            hash[rb].cnt[c[i]]--;
            c[i] = color; hash[rb].cnt[color]++;
        }
    } else {
        push_down (lb);
        for (int i=ql; i<=qr; ++i) {
            hash[lb].cnt[c[i]]--;
            c[i] = color; hash[lb].cnt[color]++;
        }
    }
}

int query(int ql, int qr, int color) {
    int lb = ql / block, rb = qr / block;
    int ret = 0;
    for (int i=lb+1; i<rb; ++i) {
        if (hash[i].col == color) {
            ret += hash[i].size;
        } else if (hash[i].col == -1 && hash[i].cnt.find (color) != hash[i].cnt.end ()) {
            ret += hash[i].cnt[color];
        }
    }
    if (lb != rb) {
        push_down (lb);
        push_down (rb);
        for (int i=ql; i<(lb+1)*block; ++i) {
            ret += (c[i] == color);
        }
        for (int i=rb*block; i<=qr; ++i) {
            ret += (c[i] == color);
        }
    } else {
        push_down (lb);
        for (int i=ql; i<=qr; ++i) {
            ret += (c[i] == color);
        }
    }
    return ret;
}

int main() {
    while (scanf ("%d%d", &n, &m) == 2) {
        for (int i=0; i<n; ++i) {
            scanf ("%d", c+i);
        }
        init_hash ();
        for (int i=0; i<m; ++i) {
            int op, ql, qr, color;
            scanf ("%d%d%d%d", &op, &ql, &qr, &color);
            if (op == 1) {
                updata (ql, qr, color);
            } else {
                printf ("%d
", query (ql, qr, color));
            }
        }
    }
    return 0;
}

暴力+贪心 D Throw nails

题意:n个人赛跑,第一秒跑f,后面每秒跑s,问每一秒跑最快的人是谁.

分析:

  方法一:考虑到f<=500,500秒前可以模拟,500秒后的状态固定了(s大的人在500秒前超过了其他人),只要sort一次就可以了.

  方法二:考虑到s<=100,对于每一个速度,设置一个优先队列保存f以及id,那么每秒跑最快的人一定是在所有速度里当前f最大中的当前最远的那个.

#include <bits/stdc++.h>

const int N = 5e4 + 5;
struct Runner {
    int f, s, id;
    bool dead;
    bool operator < (const Runner &rhs) const {
        return f > rhs.f || (f == rhs.f && id < rhs.id);
    }
}r[N];
int ans[N];

int main() {
    int T; scanf ("%d", &T);
    for (int cas=1; cas<=T; ++cas) {
        int n; scanf ("%d", &n);
        int maxv = -1, maxid = -1;
        for (int i=1; i<=n; ++i) {
            scanf ("%d%d", &r[i].f, &r[i].s);
            r[i].id = i; r[i].dead = false;
            if (r[i].f > maxv) {
                maxv = r[i].f;
                maxid = i;
            }
        }
        for (int i=1; i<=501; ++i) {
            ans[i] = maxid;
            r[maxid].dead = true;
            maxv = maxid = -1;
            for (int j=1; j<=n; ++j) {
                r[j].f += r[j].s;
                if (!r[j].dead && r[j].f > maxv) {
                    maxv = r[j].f;
                    maxid = j;
                }
            }
        }
        std::sort (r+1, r+1+n);
        int m = 501;
        for (int i=1; i<=n; ++i) {
            if (r[i].dead) {
                continue;
            }
            ans[++m] = r[i].id;
        }
        printf ("Case #%d:
", cas);
        for (int i=1; i<=n; ++i) {
            printf ("%d%c", ans[i], i == n ? '
' : ' ');
        }
    }
    return 0;
}
#include <bits/stdc++.h>

const int N = 5e4 + 5;
struct Node {
    int f, id;
    bool operator < (const Node &rhs) const {
        return f < rhs.f || (f == rhs.f && id > rhs.id);
    }
};
std::priority_queue<Node> pque[105];

int main() {
    int T; scanf ("%d", &T);
    for (int cas=1; cas<=T; ++cas) {
        int n; scanf ("%d", &n);
        for (int i=1; i<=n; ++i) {
            int f, s;
            scanf ("%d%d", &f, &s);
            pque[s].push ((Node) {f, i});
        }
        printf ("Case #%d:
", cas);
        for (int i=1; i<=n; ++i) {
            int maxd = -1, minid = N, speed = 0;
            for (int j=1; j<=100; ++j) {
                if (!pque[j].empty ()) {
                    Node u = pque[j].top ();
                    int tf = (i-1)*j+u.f;
                    if (tf > maxd || (tf == maxd && u.id < minid)) {
                        maxd = tf;
                        speed = j;
                        minid = u.id;
                    }
                }
            }
            int id = pque[speed].top ().id;
            printf ("%d%c", id, i == n ? '
' : ' ');
            pque[speed].pop ();
        }
    }
    return 0;
}

数论+BFS E Digital Square

题意:问最小的M,使得M^2%(10^x)=N

分析:这是一道数论剪枝的广搜题,如果N的最大数位是n,那么answer的最大数位应该也是n,那么可以从最小的数开始广搜,比如先确定一位数的数0,1,2…9,再在个位的基础上扩展到十位,加上0,10,20…90,然后依次下去……直到位数达到了N的位数。这里用了struct{ll a; int b;},其中a是符合要求的数,b是数位,那么剪枝的条件是a*a % 10^b == N % 10^b,证明略(可以从平方和公式入手即N^2=(X*10^m+Y)^2 N=XY(例:N=1096 X=10,Y=96 m=2或N=1096 X=109,Y=6,m=1))。最后再找出队列中剩余数的最小值即可,但要注意不满足条件的情况。

问题关键点在于N的后a位数字只由M^2后a位数字产生.

#include <bits/stdc++.h>

typedef long long ll;
const int INF = 0x7fffffff;
struct Node {
    ll x;
    int l;
};

int get_length(int n) {
    if (n == 0) {
        return 1;
    }
    int ret = 0;
    while (n) {
        ret++;
        n /= 10;
    }
    return ret;
}

ll ten_pow[12];

void prepare() {
    ten_pow[0] = 1;
    for (int i=1; i<12; ++i) {
        ten_pow[i] = ten_pow[i-1] * 10;
    }
}

int BFS(int n) {
    int len = get_length (n);
    std::queue<Node> que; que.push ((Node) {0, 0});
    ll ret = INF;
    while (!que.empty ()) {
        Node &u = que.front (); que.pop ();
        if (u.l == len) {
            ret = std::min (ret, u.x);
            break;
        }
        for (int i=0; i<10; ++i) {
            ll v = u.x + i * ten_pow[u.l];
            if (v * v % ten_pow[u.l+1] == n % ten_pow[u.l+1]) {
                que.push ((Node) {v, u.l + 1});
            }
        }
    }
    while (!que.empty ()) {
        ll t = que.front ().x; que.pop ();
        ret = std::min (ret, t);
    }
    return ret;
}

int main() {
    prepare ();
    int T; scanf ("%d", &T);
    while (T--) {
        int n; scanf ("%d", &n);
        int ans = BFS (n);
        if (ans == INF) {
            puts ("None");
        } else {
            printf ("%d
", ans);
        }
    }
    return 0;
}

01背包 F D-mail

题意:有n个范围在[-0.1, 2]的实数,问如何组合使得能尽可能接近范围为的实数D(-1, 2),实数小数点后有4位.

分析:数字*10^4转换为整数,D': (-10000, 20000), sum [200*-1000, 200*20000],取交集为[-200000, 20000],转化为正数,SHIFT=200000, [0, 220000]

#include <bits/stdc++.h>

const int N = 220000 + 5;
const int SHIFT = 200000;
const double EPS = 1e-8;
bool dp[N];
int a[205];

int change(double x) {
    if (x > 0) {
        return (int) (x * 10000 + EPS);
    } else {
        return (int) (x * 10000 - EPS);
    }
}

int main() {
    int T; scanf ("%d", &T);
    while (T--) {
        double x;
        int n, aim;
        scanf ("%lf%d", &x, &n);
        aim = change (x);
        for (int i=0; i<n; ++i) {
            scanf ("%lf", &x);
            a[i] = change (x);
        }
        std::sort (a, a+n);

        memset (dp, false, sizeof (dp));
        dp[0+SHIFT] = true;
        for (int i=0; i<n; ++i) {
            if (a[i] > 0) {
                for (int j=220000-a[i]-1; j>=0; --j) {
                    if (dp[j]) {
                        dp[j+a[i]] = true;
                    }
                }
            } else {
                for (int j=-a[i]; j<220000; ++j) {
                    if (dp[j]) {
                        dp[j+a[i]] = true;
                    }
                }
            }
        }
        double ans = 0; int now = aim + SHIFT, mv = 0;
        while (true) {
            if (now - mv >= 0 && dp[now-mv]) {
                ans = (now - mv - SHIFT) / 10000.0;
                break;
            }
            if (now + mv < 220000 && dp[now+mv]) {
                ans = (now + mv - SHIFT) / 10000.0;
                break;
            }
            mv++;
        }
        printf ("%.4f
", ans);
    }
    return 0;
}

二维SPFA G More lumber is required

题意:一张有自环的有向带权图,每条边有权值,每条边能获得10木材,问从S到T所花费的最短时间,前提是木材数>=K

分析:考虑到K<=500,所以最少需要50条边.在常规的最短路最短路径加一维为边数,即d[i][j]表示走到i时走了j步最短需要的时间,当j > k时归到d[i][k]里去,这是剪枝.然后在跑一个SPFA.

对SPFA的一个很直观的理解就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数),所以此时利用数组记录节点访问可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决方法是放弃数组,此时所需时间自然就是指数级的,所以我们不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。

#include <bits/stdc++.h>

const int N = 5e3 + 5;
const int E = 1e5 + 5;
const int INF = 0x3f3f3f3f;
struct Edge {
    int v, w, nex;
}edge[E<<1];
bool vis[N][55];
int head[N], d[N][55];
int n, m, tote;

struct Node{
    int u, s;
};

void init(void) {
    memset (head, -1, sizeof (head));
    tote = 0;
}

void add_edge(int u, int v, int w)  {
    edge[tote].v = v;  edge[tote].w = w;
    edge[tote].nex = head[u];  head[u] = tote++;
}

void SPFA(int s, int k) {
    memset (vis, false, sizeof (vis));
    memset (d, INF, sizeof (d));
    d[s][0] = 0; vis[s][0] = true;
    std::queue<Node> que; que.push ((Node) {s, 0});
    while (!que.empty ()) {
        Node &r = que.front (); que.pop ();
        vis[r.u][r.s] = false;
        for (int i=head[r.u]; ~i; i=edge[i].nex) {
            Edge &e = edge[i];
            int mins = std::min (k, r.s + 1);
            if (d[e.v][mins] > d[r.u][r.s] + e.w) {
                d[e.v][mins] = d[r.u][r.s] + e.w;
                if (!vis[e.v][mins]) {
                    vis[e.v][mins] = true;
                    que.push ((Node) {e.v, mins});
                }
            }
        }
    }
}

int main() {
    while (scanf ("%d%d", &n, &m) == 2) {
        init ();
        for (int u, v, t, i=0; i<m; ++i) {
            scanf ("%d%d%d", &u, &v, &t);
            add_edge (u, v, t);
            add_edge (v, u, t);
        }
        int S, T, K; scanf ("%d%d%d", &S, &T, &K);
        int k = K / 10;
        if (K % 10) {
            k++;
        }
        SPFA (S, k);
        if (d[T][k] == INF) {
            puts ("-1");
        } else {
            printf ("%d
", d[T][k]);
        }
    }
    return 0;
}

STL+贪心 J Template Library Management

题意:解决n个问题,解决第i个问题需要T[i]模板,手上最多只能有m块模板,问最少需要借朋友的几次.

分析:每次扔掉离下次用最远的模板,搞出每个问题的模板下次用到的时间,set维护操作,不知道为啥用pair的排序才能过.

#include <bits/stdc++.h>

const int N = 1e5 + 5;
int a[N], A[N<<1];
int pos[N<<1], nex[N], id[N];
bool ins[N<<1];
std::set<std::pair<int, int> > st;
int n, m;

int main() {
    while (scanf ("%d%d", &n, &m) == 2) {
        int tot = 0;
        for (int i=1; i<=n; ++i) {
            scanf ("%d", a+i);
            A[++tot] = a[i];
        }
        for (int i=1; i<=m; ++i) {
            A[++tot] = i;
        }
        std::sort (A+1, A+1+tot);
        tot = std::unique (A+1, A+1+tot) - (A+1);
        for (int i=1; i<=tot; ++i) {
            ins[i] = (i <= m);
        }
        for (int i=1; i<=tot; ++i) {
            pos[i] = n + 1;
        }
        for (int i=n; i>=1; --i) {
            id[i] = std::lower_bound (A+1, A+1+tot, a[i]) - A;
            nex[i] = pos[id[i]];
            pos[id[i]] = i;
        }
        st.clear ();
        for (int i=1; i<=m; ++i) {
            st.insert (std::make_pair (pos[i], i));
        }
        std::set<std::pair<int, int> >::iterator it;
        int ans = 0;
        for (int i=1; i<=n; ++i) {
            if (ins[id[i]]) {
                it = st.find (std::make_pair (pos[id[i]], id[i]));
                st.erase (it);
                pos[id[i]] = nex[i];
                st.insert (std::make_pair (pos[id[i]], id[i]));
            } else {
                ans++;
                ins[id[i]] = true;
                pos[id[i]] = nex[i];
                st.insert (std::make_pair (nex[i], id[i]));
                it = st.end ();
                it--;
                ins[it->second] = false;
                st.erase (it);
            }
        }
        printf ("%d
", ans);
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/Running-Time/p/5463583.html