萌神吃土豆007 (zoj 3676 - 3685)

貌似就因为这名字我也要做啊。。。

总结一下今天题的给我们队的感觉。。复杂度。。。不要不敢想。。


A

problem

给你n个人和T个询问,每个人有两个值pi和qi,pi表示你给他一瓶nomal可乐他给你的cup数量,qi表示你给他一瓶gift可乐他给你的cup数量。你有无限多nomal可乐和0个gift可乐。你给每个人一瓶可乐。你可以借瓶盖但是最终要还上。

T组询问每组询问一个数m,表示m个cup可以换一瓶gift可乐。问这时你最多可以有多少cup。

think

对于第i个人,你可以给他一瓶nomal可乐,得到pi个cup,也可以给他一个gift可乐得到qi-m个cup(这就是所谓的可以借瓶盖吧)

所以答案就是sum( max(pi, qi-m) )

我们按照pi-qi排序。(当然按照qi-pi也可以的)

对于m,二分找到最后一个小于等于m的位置。前面的是sum(qi-m) 后面的是sum(pi)

code

int fk[N]; //这个用于二分的数组是小队友写的。。我从不如此命名。。
struct point{
    int p, q, x, sump, sumq;
}pp[N];

bool cmp(point a, point b){
    return a.x < b.x;
}

int main(){
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF){
        for(int i = 0; i < n; ++i){
            scanf("%d%d", &pp[i].p, &pp[i].q);
            pp[i].x = pp[i].p - pp[i].q;
        }
        sort(pp, pp + n, cmp);
        pp[0].sump = pp[0].p;
        pp[0].sumq = pp[0].q;
        fk[0] = pp[0].x;
        for(int i = 1; i < n; ++i){
            pp[i].sump = pp[i-1].sump + pp[i].p;
            pp[i].sumq = pp[i-1].sumq + pp[i].q;
            fk[i] = pp[i].x;
        }
        fk[n] = INF;
        int a;
        while(m--){
            scanf("%d", &a);
            int l = 0, r = n - 1, mid = 0;
            mid = upper_bound(fk, fk + n, -a) - fk;
            if (fk[mid] > -a) mid--;
            int ans = pp[mid].sumq - a*(mid+1) + pp[n-1].sump - pp[mid].sump;
            printf("%d\n", ans);

        }
    }
	return 0;
}


B 是计算几何不会 。。 C 是队有过的没看。。DE 还没看。。


F

problem

给你n和m,n表示有n个数,m表示n里面m个1(其余是0),问这样能不能赢。和n个里面至少有几个1可以赢。

赢是这样定义的:对于n,如果里面超过一半的1,那么他就是赢。或者把n等分成a组,这a组里面有超过一半的组赢,那么n赢。n不能赢就是输了。

think

我们想的是,分组的时候,分成2的某次方和所有质数。比如n是120就分解成,8, 3, 5。然后每个数都是(a/2+1)然后乘起来。比如120就是 5*2*3 = 30. 

但是这样不对,不知道为什么。。。AC了的是爆搜,非常暴力,但是秒过了。看代码吧。

code

map<int, int> mp;

int gao(int m) {
    if(mp[m] != 0) return mp[m];
    int mm = sqrt(m + 0.5);
    int ans = m/2 + 1;
    for(int i = 2; i <= mm; ++i){
        if(m % i == 0){
            ans = min(ans, gao(i) * gao(m/i)); //这两种都AC
//            ans = min(ans, (i / 2 + 1) * gao(m / i));
//            ans = min(ans, (m / i / 2 + 1) * gao(i));
        }
    }
    return mp[m] = ans;
}
int main ()
{
    int n, m;
    while(scanf("%d%d", &m, &n) != EOF) {
        int dd = gao(m);
        if (dd > n) puts("No");
        else puts("Yes");
        printf("%d\n", dd);
    }
    return 0;
}

G

problem

给你a和b表示两个队的人数。给你n和n个数(这n个数和是a+b)表示有n堆人和每堆人的数量。

每堆人要么都是某一个队要么一队一半。

think

很简单是DP,但是我以为复杂度会太大,但是秒过了。一般滚动数组会减慢时间。所以我以为会T所以滚动了。50 case啊。

dp[n][a]  表示到第n堆人已经有了a个第一个队的人。

code

const int mod = 1000000007;
const int N = 222;
const int M = 100010;
int zhu[2][M];

int main(){
    int n, m, a;
    while(scanf("%d%d", &n, &m) != EOF){
        memset(zhu, 0, sizeof(zhu));
        int nn;
        scanf("%d", &nn);
        zhu[0][0] = 1;
        for(int i = 1; i <= nn; ++i){
            int ii = i & 1;
            for(int j = 0; j <= n; ++j) zhu[ii][j] = 0;
            scanf("%d", &a);
            for(int j = 0; j <= n; ++j){
                zhu[ii][j] += zhu[1-ii][j];
                if(zhu[ii][j] >= mod) zhu[ii][j] -= mod;
                if(a + j <= n) {
                    zhu[ii][a+j] += zhu[1-ii][j];
                    if(zhu[ii][a+j] >= mod) zhu[ii][a+j] -= mod;
                }
                if(a%2 == 0 && a/2 + j <= n) {
                    zhu[ii][a/2+j] += zhu[1-ii][j];
                    if(zhu[ii][a/2+j] >= mod) zhu[ii][a/2+j] -= mod;
                }
            }
        }
        printf("%d\n", zhu[nn&1][n]);
    }
	return 0;
}

I

problem

一棵树。每条边有两个值,一个是距离,一个数破坏这表边的代价。

root是所有点里面,距离最远的边最近的那个点。

要想让所有叶子和root之间不连通。求最大的最小破坏的值。

think

找root是用有up dw1 dw2那种树直径的树DP, 模版。

然后dfs一遍就找到答案了。

code

const int N = 11111;

struct point{
    int u;
    LL a, b;
    point(){};
    point(int _u, LL _a, LL _b){
        u = _u; a = _a; b = _b;
    }
};

vector<point > edge[N];
LL up[N];
LL dw1[N];
LL dw2[N];
int root;
LL tmp;
LL ans;

void dfs1(int s, int pre){
    dw1[s] = dw2[s] = 0;
    int i, ss;
    LL dd;
    for(int i = 0, len = edge[s].size(); i < len; ++i){
        ss = edge[s][i].u;
        if(ss == pre) continue;
        dfs1(ss, s);
        dd = edge[s][i].a + dw1[ss];
        if(dd >= dw1[s]){
            dw2[s] = dw1[s];
            dw1[s] = dd;
        }
        else if(dd > dw2[s]){
            dw2[s] = dd;
        }
    }
}

void dfs2(int s, int pre, LL D){
    int i, ss;
    LL dd;
    if(dw1[pre] == dw1[s] + D) up[s] = D + max(up[pre], dw2[pre]);
    else up[s] = D + max(up[pre], dw1[pre]);
    LL tt = max(dw1[s], up[s]);
    if(tt < tmp){
        root = s;
        tmp = tt;
    }
    for(int i = 0, len = edge[s].size(); i < len; ++i){
        ss = edge[s][i].u;
        if(ss == pre) continue;
        dd = edge[s][i].a;
        dfs2(ss, s, dd);
    }
}

void dfs3(int s, int pre, LL D){
    int len = edge[s].size();
    if(len == 1){
        ans = max(ans, D);
        return;
    }
    for(int i = 0; i < len; ++i){
        int ss = edge[s][i].u;
        if(ss == pre) continue;
        dfs3(ss, s, min(D, edge[s][i].b));
    }
}

int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        if(n == 0){
            puts("0");
            continue;
        }
        int u, v;
        LL a, b;
        for(int i = 0; i <= n; ++i) edge[i].clear();
        for(int i = 1; i < n; ++i){
            scanf("%d%d%lld%lld", &u, &v, &a, &b);
            edge[v].push_back(point(u, a, b));
            edge[u].push_back(point(v, a, b));
        }
        ans = 0;
        tmp = 1000000000;
        root = 1;
        dfs1(1, 0);
        dfs2(1, 0, 0);
        dfs3(root, 0, 1000000000LL);
        printf("%lld\n", ans);
    }

	return 0;
}


J

problem

给你一个数n。要让c0*n^3 + c1*(n-1)^3 …… c(n-1)*1^3的绝对值最小。

其中c0, c1, ……c(n-1)是‘+’ 或‘-’

think

传说超过15那个式子就是0或1.所以不会有负数出现。

然后又是我们以为复杂度会非常高的爆搜。不会想想中间确实会很小。

code

//别人的code
typedef long long ll;
const char ans[20][20]={
"",
"+",
"-+",
"-++",
"-+++",
"-++++",
"-+++-+",
"+---+++",
"+--+--++",
"+--+-+--+",
"-+-+++++++",
"-++-+--++++",
"+--++---+-++",
"--+++++-+++++",
"-++-+--++--+-+"
};
char s[10010];
ll s3[10010];//预处理三次方
bool dfs(ll sum,int n){
    for(int i=n;i>0;i--)if(sum>=s3[i]){
        if(sum - s3[i]==0 || dfs(sum - s3[i],i-1)){
            s[i-1]='+';
            return true;
        }
    }
    return false;
}
void solve(ll n){
    for(int i=0;i<n;i++)s[i]='-';
    ll sum=n*n*(n+1)*(n+1)/8;
    dfs(sum,n);
    for(int i=n-1;i>=0;i--)putchar(s[i]);
    puts("");
}
int main()
{
    for(ll i=1;i<=10000;i++) s3[i]=i*i*i;
    int n;
    while(~scanf("%d",&n)){
        if(n<15)puts(ans[n]);
        else solve(n);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值