2015 亚洲区域赛长春赛区网络赛解题报告(9/13)

这场比赛由东北师范大学出题,题目共13道,冠军过了12道,出线需要6道,整体上难度偏易。

1001 Alisha’s Party

题意:Alisha有n个朋友来参加她的party,每个朋友来的时候会带一个价值为vi的礼物,Alisha会为她们开m次门,但是每次开门会只能进ai个人,而且是礼物价值高的人先进,如果礼物价值一样,那么先来的先进。之后有q次询问,问第bi个进来的人是谁。

思路:纯模拟题,也没什么坑点,要说的话就是询问的时间不一定是有序的。

朋友以礼物价值为先,到来次序为后进行排序,在下次询问时间之前让这些人进来,按次序出队即可。

另外这题最坑的地方是比赛的时候这份代码提交并没有过,师兄拿去改了个输出就过了,2000ms的题跑了1950ms也是无奈,题目放出来后1200ms+就过掉了。。

/****************************************************
  >Created Date: 2015-09-13-23.47.35
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 1e5 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;

struct Person{
    int val, idx;
    string name;
    bool operator < (const Person &i) const {
        return val == i.val ? idx > i.idx : val < i.val;
    }
}person[MAXN];
struct Open{
    int time, k;
    bool operator < (const Open &i) const {
        return time < i.time;
    }
}open[MAXN];
void print(string a){
    for(int i = 0; i < a.size(); i++)
        putchar(a[i]);
}
priority_queue<Person> pq;
string ans[MAXN];
char name[10000];
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        while(!pq.empty()) pq.pop();
        int n, k, nq;
        scanf("%d %d %d", &n, &k, &nq);
        for(int i = 0; i < n; i++) {
            scanf("%s", name);
            person[i].name = name;
            scanf("%d", &person[i].val);
            person[i].idx = i;
        }
        for(int i = 0; i < k; i++){
            scanf("%d %d", &open[i].time, &open[i].k);
        }
        open[k].time = n;
        open[k].k = n;
        k++;
        sort(open, open + k);
        int now = 0, next_open = open[0].time, p = open[0].k;
        int ansidx = 1, askidx = 0;
        while(now < n) {
            while(now < n && now < next_open) pq.push(person[now++]);
            while(p--){
                ans[ansidx++] = pq.top().name;
                pq.pop();
                if(pq.empty()) break;
            }
            askidx++;
            next_open = open[askidx].time;
            p = open[askidx].k;
        }
        while(!pq.empty()){
            ans[ansidx++] = pq.top().name;
            pq.pop();
        }
        while(nq--){
            int a;
            scanf("%d", &a);
            print(ans[a]);
            if(nq != 0) putchar(' ');
        }
        putchar('\n');
    }
    return 0;
}

1002 Ponds

题意:给若干点和若干边,每个点有一个权值,删掉边小于2的点,问剩下的所有点集中,点的数目为奇数的所有集合的权值的和是多少。

思路:这题居然读错题了。。以为是问环的权值,其实只要按拓扑序删点然后并查集找集合就可以了。。

/****************************************************
  >Created Date: 2015-09-14-01.39.52
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 3e5 + 5;
const int MAXN = 1e4 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;

int fa[MAXN], set_size[MAXN], tol, head[MAXN], degree[MAXN];
LL valsum[MAXN];
bool vis[MAXN];

int findfa(int a){
    return a == fa[a] ? a : fa[a] = findfa(fa[a]);
}

void unit(int a, int b){
    int fa1 = findfa(a), fa2 = findfa(b);
    int minn = min(fa1, fa2);
    int another = minn ^ fa1 ^ fa2;
    set_size[minn] += set_size[another];
    valsum[minn] += valsum[another];
    fa[another] = minn;
}

struct Edge{
    int to, next;
}edge[MAXM];

void init(){
    tol = 0;
    memset(head,-1,sizeof(head));
    for(int i = 1; i < MAXN; i++) fa[i] = i, set_size[i] = 1;
    memset(degree, 0, sizeof degree);
    memset(vis, 0, sizeof vis);
}

void addedge(int u,int v){
    edge[tol].to = v, edge[tol].next = head[u], head[u] = tol++;
    edge[tol].to = u, edge[tol].next = head[v], head[v] = tol++;
    degree[u]++, degree[v]++;
}

void dfs(int now){
    if(degree[now] <= 0) vis[now] = true;
    else if(degree[now] == 1){
        vis[now] = true;
        for(int i = head[now]; i != -1; i = edge[i].next){
            int v = edge[i].to;
            if(!vis[v]){
                degree[v]--;
                if(degree[v] == 1) dfs(v);
                else if(degree[v] <= 0) vis[v] = true;
            }
        }
    }
}
int e[MAXM / 2][2];
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        init();
        int n, m;
        scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%I64d", &valsum[i]);
        for(int i = 0; i < m; i++){
            int a, b;
            scanf("%d %d", &a, &b);
            addedge(a, b);
            e[i][0] = a, e[i][1] = b;
        }
        for(int i = 1; i <= n; i++) dfs(i);
        for(int i = 0; i < m; i++){
            int u = e[i][0], v = e[i][1];
            if(vis[u] || vis[v]) continue;
            if(findfa(u) != findfa(v)) unit(u, v);
        }
        LL ans = 0;
        for(int i = 1; i <= n; i++){
            if(findfa(i) == i && vis[i] == false && set_size[i] % 2 == 1)
                ans += valsum[i];
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

1003 Aggregated Counting

题意:给一个序列,1,2,2,3,3,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8....

数列规则直接看题目吧:

The sequence is generated by the following scheme.
1. First, write down 1, 2 on a paper.
2. The 2nd number is 2, write down 2 2’s (including the one originally on the paper). The paper thus has 1, 2, 2 written on it.
3. The 3rd number is 2, write down 2 3’s. 1, 2, 2, 3, 3 is now shown on the paper.
4. The 4th number is 3, write down 3 4’s. 1, 2, 2, 3, 3, 4, 4, 4 is now shown on the paper.
5. The procedure continues indefinitely as you can imagine. 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, . . . .

这个序列叫自描述序列(Self-Describing Sequence)或Golomb's Sequence。

定义一种运算f(n),返回的是数值n的最后一个位置,如f(2) = 3,f(3) = 5,f(5) = 11。

求f(f(n))(1<= 1e9)

思路:

将相同的数字合并后,a[i] = 1,2,2,3,3,4,4,4,5,5,5,6,6,6...就变成了b[i] = 1, 3,5,8,11, 14...

显然这个结果就是我们要求的f(n)。

然而n最大可以有1e9,时间上和空间上都达不到。

但是考虑每个数字出现的次数形成的数列aa[i],会发现数列aa[i]与数列a[i]相同,再做数列bb[i]是出现次数少于等于i的数字共有多少种,发现bb[i]与b[i]相同,这样就确定了n在原数列a[i]中出现多少次。

再考虑将出现次数相同的数字合并,则a[i] = 1,2,2,3,3,4,4,4,5,5,5,6,6,6,6...就变成了c[i] = 1, 5, 11...c[i]表示的就是出现次数为i的数字的最后位置,其中c[1]包含数字1,c[2]包含数字2、3,c[3]包含数字4、5...

发现c[i]就是f(f(n)),也就是出现次数为i的数字的最后位置。

但是数组不能开到1e9,所以再考虑:每个c[i]中包含的数字都会影响一部分数,比如c[2]中有2、3,那么后面就会有2个数出现2次,有2个数出现3次,它在a[i]中的序列长度就是2*(2+3);同理c[3]就是3*(4+5)。

发现f(n)是出现次数为n的最后一个数,那么f(f(n))就是出现次数为n的最后一个数的位置。

所以可以通过b[i]求出n的出现次数,改进后的c[i]求出出现i次的数的最后位置。

/****************************************************
  >Created Date: 2015-09-18-22.44.39
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 5e5 + 5;
const int MAXN = 1e5 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int MOD = 1000000007;
long long a[MAXM], b[MAXM], c[MAXM], d[MAXM];
long long l[MAXM], r[MAXM];
int func(int n){
    long long id = lower_bound(b, b + MAXM, n) - b - 1;
    long long ret = c[id];
    for(int i = l[id + 1]; i <= n; i++){
        ret = ret + i * (id + 1);
        ret %= MOD;
    }
    return (int)ret;
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
    #endif
    a[1] = 1, a[2] = 2, a[3] = 2;
    int idx = 3, val = 3;
    for(int i = 4; i < MAXM;){
        for(int j = 0; j < a[idx] && i < MAXM; j++){
            a[i++] = val;
        }
        val++, idx++;
    }
    for(int i = 1; i < MAXM; i++){
        b[i] = b[i - 1] + a[i];
        l[i] = r[i - 1] + 1;
        r[i] = l[i] + a[i] - 1;
        c[i] = (c[i - 1] + ((l[i] * a[i]) % MOD + ((a[i] * a[i] - a[i]) / 2)) * i) % MOD;
    }
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        printf("%d\n", func(n));
    }
    return 0;
}

1005 Traval

题意:询问有多少点对之间的路径中最长边小于等于qi。

思路:并查集即可,将边和询问排序,对于每个询问,将小于等于它的边加入并查集,并记录每个集合的点数,当有新的点加入集合时增加了多少点对加入当前询问答案中,这样可以加速计算。最后统计答案的时候记得当前答案加上上一个询问的答案。

/****************************************************
  >Created Date: 2015-09-14-17.40.05
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <stack>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 20005;
const double PI = acos(-1.0);
const double eps = 1e-6;

int fa[MAXN], sz[MAXN];
long long ans[MAXN];

void init(){
    memset(ans, 0, sizeof ans);
    for(int i = 1; i <= MAXN; i++) fa[i] = i, sz[i] = 1;
}
int findfa(int a){
    return a == fa[a] ? a : fa[a] = findfa(fa[a]);
}
void unit(int a, int b, int id){
    int t1 = findfa(a), t2 = findfa(b);
    int minn = min(t1, t2);
    int another = minn ^ t1 ^ t2;
    ans[id] += sz[minn] * sz[another] * 2;
    sz[minn] += sz[another];
    fa[another] = minn;
}
struct Edge{
    int a, b, v;
    bool operator < (const Edge &i) const {
        return v < i.v;
    }
}edge[MAXM];
struct Query{
    int d, idx;
    bool operator < (const Query &i) const {
        return d < i.d;
    }
}query[MAXN];

int main(){
    #ifndef ONLINE_JUDGE
	freopen("in.in", "r", stdin);
    #endif
    int t;
    scanf("%d", &t);
    while(t--){
        int n, m, q;
        scanf("%d %d %d", &n, &m, &q);
        init();
        for(int i = 0; i < m; i++) scanf("%d %d %d", &edge[i].a, &edge[i].b, &edge[i].v);
        sort(edge, edge + m);
        for(int i = 0; i < q; i++){
            scanf("%d", &query[i].d);
            query[i].idx = i;
        }
        sort(query, query + q);
        int now = 0;
        for(int i = 0; i < q; i++){
            while(edge[now].v <= query[i].d && now < m){
                int u = edge[now].a, v = edge[now].b;
                if(findfa(u) != findfa(v)) unit(u, v, query[i].idx);
                now++;
            }
        }
        for(int i = 1; i < q; i++){
            ans[query[i].idx] += ans[query[i - 1].idx];
        }
        for(int i = 0; i < q; i++)
            printf("%I64d\n", ans[i]);
    }
    return 0;
}

1006 Favorite Donut

题意:给一个字符串环,以任意点为起点,可顺时针或逆时针走一圈,问最大字典序的起点与方向,如果有多个最大字典序,输出起点下标最小的;如果依然有多个答案,输出顺时针的。

思路:这题居然裸模拟可以过。。数据太弱了。。

正解应该是字符串最小表示法+kmp,将字符串最小表示法稍微改一下就可以求最大表示法,然后正序一次,倒序一次,得到两个最大的字符串,如果倒序的更大,那还要kmp找一下最后出现的位置,因为是倒序的,所以起点越靠后,在正序中越靠前。

纯模拟:

/****************************************************
  >Created Date: 2015-09-16-14.04.07
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 1e5 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;

string arr[MAXN];
bool cmp(string a, string b, int pos1, int pos2, int d1, int d2){
    if(a > b) return true;
    else if(a < b) return false;
    if(pos1 < pos2) return true;
    else if(pos1 > pos2) return false;
    if(d1 < d2) return true;
    else if(d1 > d2) return  false;
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
    #endif // ONLINE_JUDGE
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        string s, s2;
        char ch = 'a';
        cin >> s;
        for(int i = 0; i < n; i++) ch = s[i] > ch ? s[i] : ch;
        s = s + s;
        s2 = s;
        for(int i = 0, j = n * 2 - 1; i < j; i++, j--) swap(s2[i], s2[j]);
        string ansstr = "";
        int anspos, ansd;
        bool isfirst = false;
        for(int i = 0; i < n; i++){
            if(s[i] == ch && isfirst == false){
                isfirst = true;
                anspos = i;
                string t1 = s.substr(i, n), t2 = s2.substr(n - i - 1, n);
                if(cmp(t1, t2, i, i, 0, 1)) ansstr = t1, ansd = 0;
                else ansstr = t2, ansd = 1;
            }
            else if(s[i] == ch){
                string t1 = s.substr(i, n), t2 = s2.substr(n - i - 1, n);
                if(cmp(t1, ansstr, i, anspos, 0, ansd)) ansstr = t1, anspos = i, ansd = 0;
                if(cmp(t2, ansstr, i, anspos, 1, ansd)) ansstr = t2, anspos = i, ansd = 1;
            }
        }
        printf("%d %d\n", anspos + 1, ansd);
    }
    return 0;
}

字符串最小表示法+kmp:

/****************************************************
  >Created Date: 2015-09-16-16.25.18
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 1e5 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;
string getmaxstring(string s, int &pos){//字符串最大表示法
    int i, j, k, l;
    int N = s.length();
    s += s;
    for(i = 0, j = 1; j < N ; ){
        for(k = 0; k < N && s[i + k] == s[j + k]; k++);
        if(k >= N) break;
        if(s[i + k] > s[j + k]) j += k + 1;//大于号改小于号就是最小表示法
        else {
            l = i + k;
            i = j;
            j = max(l, j) + 1;
        }
    }
    pos = i;
    return s.substr(i, N);
}
int Next[MAXN];
void getnext(string s) {
    int i = 0, j = -1;
    Next[0] = -1;
    s += s[0];
    int m = s.length();
    while(i < m) {
        if(j == -1 || s[j] == s[i]) {
            j++; i++;
            Next[i] = j;
        }
        else j = Next[j];
    }
}
void kmp(string s1, string s2, int &pos) {
    getnext(s2);
    s1 += s1;
    int i = pos + 1, j = 0, n = s1.length(), m = s2.length();
    while(i < n - 1) {
        if(s1[i] == s2[j] || j == -1) j++, i++;
        else j = Next[j];
        if(j == m) {//注意这里不要匹配成功就结束
            pos = i - m;
            j = Next[j];
        }
        if(i - m >= n) break;
    }
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
    #endif // ONLINE_JUDGE
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        string s;
        cin >> s;
        int pos1, pos2;
        string s1 = getmaxstring(s, pos1);
        for(int i = 0, j = s.length() - 1; i < j; i++, j--) swap(s[i], s[j]);
        string s2 = getmaxstring(s, pos2);
        kmp(s, s2, pos2);
        if(s1 > s2) cout << pos1 + 1<< " 0" << endl;
        else if(s1 < s2) cout << n - pos2 << " 1" << endl;
        else{
            if(n - pos2 < pos1) cout << n - pos2 << " 1" << endl;
            else cout << pos1 + 1<< " 0" << endl;
        }
    }
    return 0;
}

1007 The Water Problem

题意:给1000个数询问1000次范围内最大的数。

思路:全场最水的题不需要思路。

/****************************************************
  >Created Date: 2015-09-13-23.58.38
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 1005;
const double PI = acos(-1.0);
const double eps = 1e-6;

int seq[MAXN];
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        int n, nq;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &seq[i]);
        scanf("%d", &nq);
        for(int i = 0; i < nq; i++){
            int l, r;
            scanf("%d %d", &l, &r);
            int maxx = seq[l];
            for(int j = l; j <= r; j++)
                maxx = max(maxx, seq[j]);
            printf("%d\n", maxx);
        }
    }
    return 0;
}

1008 Elven Postman

题意:给一个树的遍历序列,树上每个节点的右儿子的权值都要小于它的权值,左儿子的权值都要大于它的权值,然后对于若干组询问,问从根怎么走可以走到某个节点(右东左西)。

思路:显然只要构建出树就可以从根节点二分走出路径,所以关键是如何建树。

可以发现对于每个节点:左节点的子树上节点的值范围是父节点的最大边界到父节点的值,右儿子的子树上节点的值的范围是父节点的值到父节点的最小边界,根的范围就是1(最小边界)到n(最大边界)。对于每个值,如果这个值在当前节点的范围内,就根据当前节点的值往左右儿子递归;如果不在就回溯到父节点。这样树就建成了。

/****************************************************
  >Created Date: 2015-09-14-14.15.06
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <stack>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 10000;
const double PI = acos(-1.0);
const double eps = 1e-6;
struct Tree{
    int lson, rson, val, fa;
    int minn, maxx;
}tree[MAXN];
int val[MAXN];
void buildtree(int rt, int idx, int n){
    if(idx > n) return ;
    if(val[idx] >= tree[rt].val && val[idx] <= tree[rt].maxx){
        if(tree[rt].lson == 0) {
            tree[rt].lson = idx;
            tree[idx].val = val[idx];
            tree[idx].minn = tree[rt].val;
            tree[idx].maxx = tree[rt].maxx;
            tree[idx].fa = rt;
            idx++;
        }
        buildtree(tree[rt].lson, idx, n);
    }
    else if(val[idx] <= tree[rt].val && val[idx] >= tree[rt].minn){
        if(tree[rt].rson == 0){
            tree[rt].rson = idx;
            tree[idx].val = val[idx];
            tree[idx].minn = tree[rt].minn;
            tree[idx].maxx = tree[rt].val;
            tree[idx].fa = rt;
            idx++;
        }
        buildtree(tree[rt].rson, idx, n);
    }
    else buildtree(tree[rt].fa, idx, n);
}
void dfs(int rt, int val){
    if(tree[rt].val == val) putchar('\n');
    else {
        if(val > tree[rt].val){
            putchar('W');
            dfs(tree[rt].lson, val);
        }
        else {
            putchar('E');
            dfs(tree[rt].rson, val);
        }
    }
}
int main(){
    #ifndef ONLINE_JUDGE
        freopen("in.in", "r", stdin);
    #endif
    int t;
    scanf("%d", &t);
    while(t--){
        memset(tree, 0, sizeof tree);
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &val[i]);
        tree[1].maxx = n, tree[1].minn = 1, tree[1].val = val[1];
        buildtree(1, 2, n);
        int nq;
        scanf("%d", &nq);
        while(nq--){
            int a;
            scanf("%d", &a);
            dfs(1, a);
        }
    }
    return 0;
}

1009 Food Problem

题意:有n种甜点,m种卡车,每种甜点可以提供ei种能量,需要占si点空间,共有ai个,每种卡车需要ci的费用,有提供ti点空间,共有bi个,现在问提供p点能量最少需要多少花费。

思路:对甜点和卡车分别dp,dp甜点是i点能量的最少空间,dp卡车是i的花费的最大空间,注意到每个甜点最多提供100点能量,所以用dp甜点的[p,p + 100]去匹配dp卡车即可。注意p最大是50000, 花费最大也是50000。

/****************************************************
  >Created Date: 2015-09-18-12.54.59
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 5e4 + 200;
const double PI = acos(-1.0);
const double eps = 1e-6;

struct Truck{
    int cost, sz, n;
}b[205], trucks[2000];
struct Dessert{
    int energy, sz, n;
}a[205], dsrt[2000];
int cntd, cntt;
int dpd[MAXN], dpt[MAXN];
int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
    #endif
    int t;
    scanf("%d", &t);
    while(t--){
        for(int i = 0; i < MAXN; i++) dpd[i] = INF; dpd[0] = 0;
        memset(dpt, 0, sizeof dpt);
        cntd = cntt = 0;
        int n, m, p;
        scanf("%d %d %d", &n, &m, &p);
        int top = p + 100, sumcost = 0;
        for(int i = 0; i < n; i++) {
            scanf("%d %d %d", &a[i].energy, &a[i].sz, &a[i].n);
            int v = a[i].energy, w = a[i].sz, u = 1;
            while(a[i].n){
                if(a[i].n >= u){
                    a[i].n -= u;
                    dsrt[cntd].energy = u * v;
                    dsrt[cntd].sz = u * w;
                    cntd++;
                }
                else break;
                u <<= 1;
            }
            if(a[i].n){
                dsrt[cntd].energy = a[i].n * v;
                dsrt[cntd].sz = a[i].n * w;
                cntd++;
            }
        }
        for(int i = 0; i < m; i++) {
            scanf("%d %d %d", &b[i].sz, &b[i].cost, &b[i].n);
            sumcost += b[i].cost * b[i].n;
            int v = b[i].cost, w = b[i].sz, u = 1;
            while(b[i].n){
                if(b[i].n >= u){
                    b[i].n -= u;
                    trucks[cntt].cost = u * v;
                    trucks[cntt].sz = u * w;
                    cntt++;
                }
                else break;
                u <<= 1;
            }
            if(b[i].n){
                trucks[cntt].cost = b[i].n * v;
                trucks[cntt].sz = b[i].n * w;
                cntt++;
            }
        }
        sumcost = min(sumcost, 50000);
        for(int i = 0; i < cntd; i++)
            for(int j = top; j >= dsrt[i].energy; j--){
                dpd[j] = min(dpd[j], dpd[j - dsrt[i].energy] + dsrt[i].sz);
            }
        for(int i = 0; i < cntt; i++)
            for(int j = sumcost; j >= trucks[i].cost; j--)
                dpt[j] = max(dpt[j], dpt[j - trucks[i].cost] + trucks[i].sz);
        int mincost = INF, ans;
        for(int i = p; i <= top; i++) {
            if(dpd[i] == 0) continue;
            for(int j = 0; j <= sumcost; j++)
                if(dpt[j] >= dpd[i])
                    if(j < mincost) mincost = j;
        }
        if(mincost == INF) puts("TAT");
        else printf("%d\n", mincost);
    }
    return 0;
}

1010 Unknown Treasure

题意:3个数,n,m,P,其中m不超过n,问C(m,n)%P是多少,其中P的质因子个数不超过k个并且每个pi都会给出。就是求一个大数对一个long long取模,应该是比较常见的应用。

思路:数论题,与其说难,不如说坑。

从数学题的角度来看是比较基础而且很常用的理论,这道题主要涉及到两个定理:

Lucas定理:快速求C(m,n)%p的值,p是素数。

中国剩余定理(孙子定理):x mod pi = ai,求x的最小解,其中pi两两互素。

题目是问C(m,n) % P,而P已经分解成了若干素因子pi,这样就可以用lucas定理来求C(m,n)mod pi的所有结果ai,将ai和pi带入方程组x mod pi = ai,那么C(m,n)就是方程组的一个解,而C(m,n) mod P就是方程组的最小解。

这道题最坑的地方在于,在计算过程中有可能出现long long 相乘取模的爆掉的情况,这时候就要看大数模板优不优啦!!

模板不行也可以用类似快速幂的方式取模在不爆long long的情况下求得答案。

/****************************************************
  >Created Date: 2015-09-14-00.02.37
  >My Soul, Your Beats!
****************************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

typedef long long LL;

const int INF = 1 << 30;
const long long LINF = 1LL << 50;
const int MAXM = 1e5 + 5;
const int MAXN = 1e5 + 5;
const double PI = acos(-1.0);
const double eps = 1e-6;

LL mod(LL a, LL b, LL p){//相乘求模不超long long
    LL ret = 0, q = a % p;
    while(b){
        if(b & 1){
            ret = (ret + q) % p;
        }
        q = (q + q) % p;
        b >>= 1;
    }
    return ret % p;
}
LL exp_mod(LL a, LL b, LL p) {
    LL res = 1;
    while(b != 0) {
        if(b & 1) res = mod(res, a, p);
        a = mod(a, a, p);
        b >>= 1;
    }
    return res;
}

LL Comb(LL a, LL b, LL p) {
    if(a < b)   return 0;
    if(a == b)  return 1;
    if(b > a - b)   b = a - b;

    LL ans = 1, ca = 1, cb = 1;
    for(LL i = 0; i < b; ++i) {
        ca = mod(ca, a - i, p);
        cb = mod(cb, b - i, p);
    }
    ans = mod(ca, exp_mod(cb, p - 2, p), p);
    return ans;
}

LL Lucas(LL n, LL m, LL p) {//lucas定理
     LL ans = 1;

     while(n && m && ans) {
        ans = mod(ans, Comb(n % p, m % p, p), p);
        n /= p;
        m /= p;
     }
     return ans;
}

LL exgcd(LL a, LL b, LL &x, LL &y){//扩展欧几里得
    if(b == 0){
        x = 1, y = 0;
        return a;
    }
    else {
        LL r = exgcd(b, a % b, y, x);
        y -= x * (a / b);
        return r;
    }
}
LL CRT(LL ai[], LL pi[], int n){//中国剩余定理
    LL M = 1;
    for(int i = 0; i < n; i++) M *= pi[i];
    LL ret = 0;
    for(int i = 0; i < n; i++) {
        LL x, y;
        exgcd(pi[i], M / pi[i], x, y);
        if(y < 0) y += pi[i];
        LL t = mod(M / pi[i], y, M);
        ret = (ret + mod(ai[i], t, M)) % M;
    }
    return ret;
}

LL ri[100], pi[100];
int main() {
    int t;
    scanf("%d", &t);
    while(t--){
        LL n, m, k, tmp1, tmp2;
        scanf("%I64d %I64d %I64d", &n, &m, &k);
        for(int i = 0; i < k; i++) scanf("%I64d", &pi[i]);
        for(int i = 0; i < k; i++) ri[i] = Lucas(n, m, pi[i]);
        printf("%I64d\n", CRT(ri, pi, k));
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值