0%

这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

由于比较弱的缘故,稍微涉及数学的题目就做了很久..

设计状态$dp(k,n_{1},n_{2})$为考虑前k行时,设有$n_1$列放一个炮,$n_2$列放两个炮(剩下的列不放炮)时的所有方案数

若在第$k$行放置$0$个炮

$dp(k,n_1,n_2) += dp(k-1,n_1,n_2)$

若在第$k$行放置$1$个炮

  1. 放在只有$0$个炮的列上:$dp(k,n_1,n_2) += dp(k - 1,n_1 - 1,n_2)\times (第k-1行的n_0)$
  2. 放在只有$1$个炮的列上:$dp(k,n_1,n_2) += dp(k - 1,n_1 + 1,n_2 - 1)\times (第k-1行的n_1)$

若在第$k$行放置$2$个炮

  1. 两个炮放在只有$0$个炮的列上
  2. 两个炮放在只有$1$个炮的列上
  3. 一个炮放在有$0$个炮的列上,一个炮放在有$1$个炮的列上

具体转移与约束见下代码

注意到复杂度为$O(nm^2)$

所以当$m>n$时交换一下两者可能会快一些??

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
const llint base = 9999973;
llint dp[150][150][150];
int main (){
        int n,m;
        cin >> n >> m;
        dp[0][0][0] = 1;
        for (int i = 1;i <= n;++ i){
                for (int n1 = 0;n1 <= m;++ n1){
                        for (int n2 = 0;n2 <= m - n1;++ n2){
                                //put 0 pao 
                                dp[i][n1][n2] = dp[i - 1][n1][n2];
                                //put 1 pao
                                if(n1 > 0)
                                        dp[i][n1][n2] = (dp[i - 1][n1 - 1][n2] * (m - n1 + 1 -n2) % base + dp[i][n1][n2]) % base;
                                if(n2 > 0)
                                        dp[i][n1][n2] = (dp[i - 1][n1 + 1][n2 - 1] * (n1 + 1) % base + dp[i][n1][n2]) % base;
                                //put 2 pao
                                if(n1 > 1){
                                        int n0_ = m - n1 + 2 - n2;
                                        dp[i][n1][n2] = ((dp[i - 1][n1 - 2][n2] * (n0_ * n0_ - n0_) / 2) % base + dp[i][n1][n2]) % base;
                                }
                                if(n2 > 1){
                                        int n1_ = n1 + 2;
                                        dp[i][n1][n2] = ((dp[i - 1][n1_][n2 - 2] * (n1_ * n1_ - n1_) / 2) % base + dp[i][n1][n2]) % base;
                                }
                                if(n2 > 0 && n1 > 0){
                                        int n0_ = m - n1 + 1 - n2;
                                        dp[i][n1][n2] = (dp[i - 1][n1][n2 - 1] * (n1 * n0_) % base + dp[i][n1][n2]) % base;
                                }
                        }
                }
        }
        llint ans = 0;
        for (int i = 0;i <= m;++ i)
        for (int j = 0;j <= m - i;++ j)
        ans = (ans + dp[n][i][j]) % base;
        cout << ans;

        return 0;
}

卡门――农夫约翰极其珍视的一条Holsteins奶牛――已经落了到“垃圾井”中。“垃圾井”是农夫们扔垃圾的地方,它的深度为 D(2≤D≤100)英尺。

卡门想把垃圾堆起来,等到堆得与井同样高时,她就能逃出井外了。另外,卡门可以通过吃一些垃圾来维持自己的生命。

每个垃圾都可以用来吃或堆放,并且堆放垃圾不用花费卡门的时间。

假设卡门预先知道了每个垃圾扔下的时间 t(0<t≤1000),以及每个垃圾堆放的高度 h(1≤h≤25)和吃进该垃圾能维持生命的时间 f(1≤f≤30),要求出卡门最早能逃出井外的时间,假设卡门当前体内有足够持续 10小时的能量,如果卡门 10小时内没有进食,卡门就将饿死。

是一道经典的dp题,并没有太大的难度,但有值得注意的地方

题目中存在三个元素:时间,高度,生命

其中生命和时间都处于时间轴中,显然依照时间轴划分阶段,根据直觉应当以垃圾掉落时间划阶段

接着生命和高度都可以作为状态.

如果以生命作为状态

  1. 数据范围大,导致复杂度(常数)大
  2. 处于时间轴中,设计转移策略时可能干扰思路
  3. 转移策略较复杂,且不方便枚举

总之显得很不自然

所以应当选定高度作为状态

接着设计转移策略时:

  1. 填表法:注意到如果高度大于D(即卡门已经得救时),难以枚举
  2. 刷表法:自然且方便判定死亡或得救

总体上是一道考验代码能力的题目.

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
int xx[2][200],*dp[2];
struct xxx{
        int t,f,h;
        bool operator < (const xxx &be)const{
                if (t != be.t)
                        return t < be.t;
                if (f != be.t)
                        return f > be.f;
                return h > be.f;
        }
}tra[200];
int main (){
        int D,G,ans = 0x3f3f3f3f,ans2 = 0;
        cin >> D >> G;
        for (int i = 1;i <= G;++ i)
                scanf("%d%d%d",&tra[i].t,&tra[i].f,&tra[i].h);
        sort(tra + 1,tra + 1 + G);
        memset(xx,-1,sizeof(xx));
        dp[0] = xx[0];dp[1] = xx[1];
        dp[1][0] = 10;
        for (int i = 1;i <= G;++ i){
                swap(dp[0],dp[1]);
                for (int j = 0;j < D;++ j){
                        if (dp[0][j] < tra[i].t)
                                continue;
                        if (j + tra[i].h >= D) ans = min(ans,tra[i].t);
                        dp[1][j + tra[i].h] = max(dp[1][j + tra[i].h],dp[0][j]);
                        dp[1][j] = max(dp[1][j],dp[0][j] + tra[i].f);
                        ans2 = max(ans2,dp[1][j]);
                }
        }
        if (ans == 0x3f3f3f3f){
                cout << ans2;
                return 0;
        }
        cout << ans;
        return 0;
}

以上.

恰逢 $H$ 国国庆,国王邀请 $n$ 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 $n$ 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

又是一道主程序十分钟高精度一小时的题..

题意转化为

每位大臣获得的金币数分别是:该大臣及前面的所有人的左手上的数的乘积除以他自己左右手上的数之和

显然满足贪心:被除数确定时,需要让除数尽量大

依左右手上数之和排序即可

完整代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
struct bigint {
        llint str[3000],len_;
        bigint operator = (llint al){
                len_ = 0;
                memset(str,0,sizeof(str));
                while (al){
                        str[len_++] = al % 10000;
                        al /= 10000;
                }
                return *this;
        }
        bigint operator *= (llint al){
                str[0] *= al;
                for (int i = 1;i <= len_ ;i ++ ){
                        str[i] = (str[i] * al) + str[i - 1] / 10000;
                        str[i - 1] %= 10000;
                }
                len_ += (str[len_] > 0);
                return *this;
        }
        bigint operator / (llint al){
                bigint ans;
                llint handle = 0;
                for (int i = len_ - 1;i >= 0;-- i){
                        handle = handle * 10000 + str[i];
                        ans.str[i] = handle / al;
                        handle %= al;
                }
                ans.len_ = len_;
                while(!ans.str[ans.len_ - 1])
                        ans.len_ --;
                return ans;
        }
        bool operator < (const bigint &al)const{
                if (len_ != al.len_) return len_ < al.len_;
                for (int i = len_ - 1;i >= 0;--i)
                        if (str[i] != al.str[i]) return str[i] < al.str[i];
                return 0;
        }
        void print(){
                if (!len_) {
                        printf("0\n");
                        return ;
                }
                printf("%lld",str[len_ - 1]);
                for (int i = len_ - 2;i >= 0;--i){
                        if (str[i] < 1000) putchar('0');
                        if (str[i] < 100) putchar('0');
                        if (str[i] < 10) putchar('0');
                        printf("%lld",str[i]);
                }
                printf("\n");
        }
};

struct  xxx{
        llint a,b;
        bool operator < (const xxx &be)const{
                if (a * b != be.a * be.b)
                        return a * b < be.a * be.b;
                return a < be.a;
        }
}arr[20000];

int main (){
        llint n;
        cin >> n;
        for (int i = 0;i <= n;++ i)
                scanf("%lld%lld",&arr[i].a,&arr[i].b);
        bigint mul,ans;
        mul = arr[0].a,ans = (llint)0;
        sort(arr + 1,arr + 1 + n);
        for (int i = 1;i <= n;++ i)
                mul *= arr[i].a,
                ans = max(ans,mul / (arr[i].a * arr[i].b));
        ans.print();
        return 0;
}

以上.

小张最近发表了一篇论文,有一个神秘人物要给小张学院发奖学金。小张学院有C名学生,要从中挑出N个。这个神秘人物爱好奇特,他希望得到奖学金的同学的成绩的中位数尽可能大,但同时,他们的奖学金总额不能超过F。

观察题目,显然应当将学生依照成绩排序

当成绩中位数确定时,应选取中位数两侧金额最小的学生,满足贪心原则

题意转化为

求中位数两端学生中金额前n/2小的金额之和

区间第k小问题,显然选用主席树解答

从大到小枚举中位数,选取第一个满足条件的即可

注意:

本题对空间要求严格,

  1. 如果预先求出所有从0开始的前缀和从c开始的所有后缀和的树版本,必然MLE(亲测)
  2. 如果只预处理前(后)缀和树,另一部分用完整树与之相减,且在枚举中位数时动态建树,可以勉强卡过(笔者的解法)
  3. 注意到中位数无法二分枚举,所以实际上只用到了当前版本和上一个版本的树,更早的树应当被销毁!

所以当笔者写完后发现根本不需要主席树……..

笔者代码如下(注意数据命名与题面不符):

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
int set_[200010],cnt[200010];
struct node{
        node *lson,*rson;
        int val,sum_;
        node(int ql,int qr,bool full){
                // build a void tree
                if(ql == qr){
                        lson = rson = NULL;
                        val = full * cnt[ql];sum_ = val * set_[ql];
                }
                else{
                        int mid = ((qr - ql) >> 1) + ql;
                        lson = new node(ql,mid,full);
                        rson = new node(mid + 1,qr,full);
                        val = full * (lson->val + rson->val);
                        sum_ = full * (lson->sum_ + rson->sum_);
                }
        }
        node(node *ori,node *ls,node *rs,int ival){
                lson = ls;rson = rs;
                val = ori->val + 1;sum_ = ori->sum_ + set_[ival];
        }
        node* insert(int ival,int l,int r){
                int mid = ((r - l) >> 1) + l;
                if (ival == l && ival == r)
                        return new node(this,NULL,NULL,ival);
                if (ival <= mid)
                        return new node(this,lson->insert(ival,l,mid),rson,ival);
                else 
                        return new node(this,lson,rson->insert(ival,mid + 1,r),ival);
        }


}*full,*suf[200010];
int query(node *op,int k,int l,int r){
        int mid = ((r - l) >> 1) + l;
        if (l == r)
                return k * set_[l];
        if (k <= op->lson->val)
                return query(op->lson,k,l,mid);
        else 
                return op->lson->sum_ + query(op->rson,k - op->lson->val,mid + 1,r);
}
int query2(node *op,node *ff,int k,int l,int r){
        int mid = ((r - l) >> 1) + l;
        if (l == r)
                return k * set_[l];
        if (k <= ff->lson->val - op->lson->val)
                return query2(op->lson,ff->lson,k,l,mid);
        else 
                return ff->lson->sum_ - op->lson->sum_ + query2(op->rson,ff->rson,k -(ff->lson->val -  op->lson->val),mid + 1,r);
}

struct xxx{
        int score,money;
        bool operator < (const xxx &be)const{
                return score < be.score;
        }
}data[200010];
int main (){
//      freopen("wb.in","r",stdin);
        //readin
        int n,k,p;
        cin>> k >> n >> p;
        for (int i = 1;i <= n;++ i){
                scanf("%d%d",&data[i].score,&data[i].money);
        }
        sort(data + 1,data + 1 + n);
        //discrete
        for (int i = 1;i <= n;++ i)
                set_[i] = data[i].money;
        sort(set_ + 1,set_ + 1 + n);
        int size_ = unique(set_ + 1,set_ + 1 + n) - set_ - 1;
        for (int i = 1;i <= n;++ i)
                data[i].money = lower_bound(set_ + 1,set_ + size_ + 1,data[i].money) - set_,
                cnt[data[i].money] ++;
        //build trees
        suf[n + 1] = new node (1,size_,0);
        full = new node (1,size_,1);
        for (int i = n;i > n - (k >> 1);-- i)
                suf[i] = suf[i + 1]->insert(data[i].money,1,size_);
        //query
        for (int i = n - (k >> 1);i > (k >> 1);-- i){
                suf[i] = suf[i + 1]->insert(data[i].money,1,size_);
                if(query2(suf[i],full,k >> 1,1,size_) + query(suf[i + 1],k >> 1,1,size_) + set_[data[i].money] <= p){
                        cout << data[i].score;
                        return 0;
                }
        }
        cout << -1 ;
        return 0;
}

以上.

给一个数列,询问某区间内第k小

本文大量参考MenCi的笔记

权值线段树

将数据作为标号,数据在数列中出现的次数作为权值,得到一组新数据.对该数据建立线段树.

显然可以在该权值线段树上查找第k小:

  1. 若k小于等于左子树权值,对左子树查找第k小
  2. 若k大于左子树权值,对右字数查找第k - val(left_son)小

如果数据极分散,将浪费大量空间.考虑离散化

数据离散化

设原始数据arr.建立一个备份set_

对set_先后进行排序,去重,将数据有序化

对arr中每个元素,查找在set中的位置,完成离散

//discrete
        memcpy(set_,arr,(n + 1) * sizeof(int));
        sort(set_ + 1,set_ + 1 + n);
        int *set_end = unique(set_ + 1,set_ + 1 + n);
        for (int i = 1;i <= n;++ i)
                arr[i] = lower_bound(set_ + 1,set_end,arr[i]) - set_;

主席树

主席树是包含多个历史版本的线段树.因此每颗线段树形态完全相同

当一颗线段树发生改变时,改变节点到根节点的路径全部复制一遍.查询时从第t个根节点查询得到的就是第t版本的线段树

前缀和

注意到本题如果对所有区间[l,r]都增加一个树版本,共耗费时间将为O(n^2 * logn)

事实上查找第k大所需的信息只是某区间的元素数量(某结点的权值),注意到区间元素数量可以由前缀和维护.

事实上由于每颗树形态相同,整个主席树都可以由前缀和维护.版本[l,r]的树实为树[0,r] - 树[0,l - 1]

总复杂度降为O(nlogn)

完整代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
int arr[400000];
struct node{
        node *lson,*rson;
        int l,r,val;
        node(int ql,int qr){
                // build a void tree
                l = ql;r = qr;
                val = 0;
                if(ql == qr){
                        lson = rson = NULL;
                }
                else{
                        int mid = ((r - l) >> 1) + l;
                        lson = new node(ql,mid);
                        rson = new node(mid + 1,qr);
                }
        }
        node(node *ori,node *ls,node *rs){
                lson = ls;rson = rs;
                l = ori->l;r = ori->r;val = ori->val + 1;
        }

        void maintain(){
                val = lson->val + rson->val;
                return ;
        }
        node* insert(int ival){
                int mid = ((r - l) >> 1) + l;
                if (ival == l && ival == r)
                        return new node(this,NULL,NULL);
                if (ival <= mid)
                        return new node(this,lson->insert(ival),rson);
                else 
                        return new node(this,lson,rson->insert(ival));
        }

}*roots[4000000];
int query(node *ql,node *qr,int k){
        if(ql->l == ql->r)
                return qr->l;
        int lval = qr->lson->val - ql->lson->val;
        if (k <= lval)
                return query(ql->lson,qr->lson,k);
        else
                return query(ql->rson,qr->rson,k - lval);
}
int main (){
        int m,n;
        static int set_[400000];
        cin >> n >> m;
        arr[0] = -0x7fffffff;
        for (int i = 1;i <= n;++ i)
                scanf("%d",&arr[i]);
        //discrete
        memcpy(set_,arr,(n + 1) * sizeof(int));
        sort(set_ + 1,set_ + 1 + n);
        int *set_end = unique(set_ + 1,set_ + 1 + n);
        for (int i = 1;i <= n;++ i)
                arr[i] = lower_bound(set_ + 1,set_end,arr[i]) - set_;
        //build trees
        int size_ = set_end - set_ - 1;
        roots[0] = new node(1,size_);
        for (int i = 1;i <= n;++ i)
                roots[i] = roots[i - 1]->insert(arr[i]);
        //query
        int beg,end,k;
        for (int i = 1;i <= m;++ i){
                scanf("%d%d%d",&beg,&end,&k);
                printf("%d\n",set_[query(roots[beg - 1],roots[end],k)]);

        }
        return 0;
}

以上.