大工之星编程挑战赛第五周题解

题目链接

D题题意出锅了,在此再次向各位选手表示道歉。

写在前面的废话
出题容易验题难,写题解更难,鄙人第一次写详细题解,表达能力可能有所欠缺,有些知识点附上了他人的文章链接,希望有助于理解www。
BCD题题解分别由wph,zzl,syd编写,KLM题题解由gjp编写,其余的7道题为鄙人编写,在此直接贴过来了他们写的题解。
难度

本人认为难度排序:A<D<C<B<E<F<H=I<J=G<K=L=M
实际上从过题人数来看中间和后面几道题的难度确实差别不大,过的人数相差不大,但B和D过的人数比我预期要少,可能也是受到题意的问题影响。

A.提瓦特的春节

签到题,输出16即可,提前祝大家春节快乐。

代码

#include<bits/stdc++.h>
#define Happy int main(){
#define Spring std::cout<<16;
#define Festival return 0;}

Happy Spring Festival

B.考试周

考察算法:
基础动态规划
思路:
建立一个 f f f 数组, f [ i ] f[i] f[i]表示当第 i i i 位同学传递物资,前 i − 1 i - 1 i1 位同学最少付出的代价。容易得到状态转移方程
f [ i ] = m i n f [ k ] ( i − m ≤ k ≤ i − 1 ) + w [ i ] f[i]=min{f[k]}(i-m \leq k \leq i-1)+w[i] f[i]=minf[k](imki1)+w[i]
Case 1:
暴力地对每个 i i i 扫前面的 m m m 个同学(如果前面有足够的人),时间复杂度 O ( n m ) O(nm) O(nm)
Case 2:
单调队列优化,时间复杂度 O ( n ) O(n) O(n)
下面给出暴力代码,感兴趣的选手如果还不会的话可以学习一下单调队列优化

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 200 + 10;
int n, m, t, k;
int a[N], f[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin >> n >> m;
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
        if (i <= m) f[i] = a[i];
    }
    for (int i = m + 1;i <= n;i++) {
        int res = inf;
        for (int j = max(i - m, 0);j < i;j++) {
            res = min(res, f[j]);
        }
        f[i] = res + a[i];
    }
    int ans = inf;
    for (int i = n - m + 1;i <= n;i++) {
        ans = min(ans, f[i]);
    }
    cout << ans << endl;
    return 0;
}

C.No Acyclic

考察算法:
简单模拟
思路:
只需从头到尾遍历该字符串,并建立一个 v i s vis vis 数组,将需要删除的标记即可。
时间复杂度: O ( n ) O(n) O(n)

代码

#include <bits/stdc++.h>
using namespace std;
bool vis[1010];
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    string s;
    cin >> s;
    int n = s.length();
    for(int i = 0; i <= n - 6; i++) {
        if((s[i]=='a'||s[i]=='A')&&(s[i+1]=='c'||s[i+1]=='C')&&(s[i+2]=='y'||s[i+2]=='Y')&&(s[i+3]=='c'||s[i+3]=='C')&&(s[i+4]=='l'||s[i+4]=='L')&&(s[i+5]=='i'||s[i+5]=='I')&&(s[i+6]=='c'||s[i+6]=='C')) {
            for(int j = 0; j <= 6; j++)
                vis[i+j] = 1;
        }
    }
    for(int i = 0; i < n; i++) {
        if(!vis[i]) cout << s[i];
    }
    cout << '\n';
    return 0;
}

D.国王的游戏

考察算法:
贪心,乘法逆元
思路:
易知若要得到奖赏最大,需要右手数字最小的大臣站在队伍最后。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
int mod = 100003;
ll fastPower(ll base, ll power) {
    ll result = 1;
    while (power > 0) {
        if (power & 1) { 
            result = result * base % mod;
        }
        power >>= 1;
        base = (base * base) % mod;
    }
    return result;
}
void solve()
{
    int n;
    cin >> n;
    ll a, b;
    ll bmin=1000007;
    cin >> a >> b;
    ll ans = a;
    for (int j = 1; j <= n; j++)
    {
        cin >> a >> b;
        ans *= a;
        ans%=mod;
        bmin = min(bmin, b);
    }
    cout << ans * fastPower(bmin,mod-2)%mod;
}
int main()
{
    solve();
    return 0;
}

E.学以致用

考察算法:
dfs,最优二叉树
思路:
在建立哈夫曼树的过程中加边,然后用dfs搜索叶子节点同时累计答案。

代码

#include<iostream>
#include<queue>
using namespace std;
#define endl "\n"
#define ull unsigned long long
#define ll  long long
#define INF 0x3f3f3f3f
const ll N = 1e5 + 5;
const ll mod = 1000000007;
const long double eps = 1e-6;
int head[N];
struct Edge {
	int to, next;
}e[105];
int cnt = 0;
void add(int u, int v) {
	e[++cnt].to = v;
	e[cnt].next = head[u];
	head[u] = cnt;
}
int ans = 0;
void  dfs(int s, int c) {
	if (head[s] == 0)
		ans += s * c;
	for (int i = head[s]; i; i = e[i].next) {
		dfs(e[i].to, c + 1);
	}
}
void solve() {
	int  n;
	cin >> n;
	priority_queue<int, vector<int >, greater<int > > q;
	for (int i = 1; i <= n; i++) {
		int tmp;
		cin >> tmp;
		q.push(tmp);
	}
	for (int i = 1; i < n; i++) {
		int a = q.top();
		q.pop();
		int b = q.top();
		q.pop();
		q.push(a + b);
		add(a + b, a);
		add(a + b, b);
	}

	dfs(q.top(), 0);
	cout << ans << endl;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	solve();
	return 0;
}

F.平稳变化

考察算法:
构造
思路:
首先考虑什么情况下会输出"Impossible",当且仅当存在 i i i 使得 p i 与 p i + 1 p_i与p_{i+1} pipi+1间可以存在的数字数量小于 a i 与 a i + 1 a_i与a_{i+1} aiai+1间的数字数量,即存在 i i i 使得 p i + 1 − p i < a b s ( a i + 1 − a i ) p_{i+1}-p_i < abs(a_{i+1}-a_i) pi+1pi<abs(ai+1ai)
接下来我们尝试构造该序列的最大可能数字,观察到,若第3个数为3,第7个数字也为3,我们可以构造第四个数为4,第五个数为5,第六个数字为4。得出3-7之间的最大值为5,也就是说,对于相邻的两个 p i 和 p i + 1 p_i和p_{i+1} pipi+1,我们尝试先增后减,每个区间得到一个最大值,容易证明这是正确的。
最后不要忘记考虑头和尾,将这 m + 1 m + 1 m+1个区间的最大值比较,得出整个数列的最大值,即为所求。
时间复杂度: O ( m ) O(m) O(m)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
int p[maxn], a[maxn];
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    int ans = 0;
    for(int i = 1; i <= m; i++) {
        cin >> p[i] >> a[i];
        ans = max(ans, a[i]);
    }
    ans = max(ans, a[1] + p[1] - 1);
    ans = max(ans, a[m] + n - p[m]);
    for(int i = 1; i < m; i++) {
        if(p[i+1]-p[i] < abs(a[i+1]-a[i])) {
            cout << "IMPOSSIBLE" << '\n';
            return 0;
        }
        if(p[i+1] >= p[i]) {
            ans = max(ans, (p[i+1]-p[i]+a[i+1]-a[i])/2+a[i]);
        }
        else {
            ans = max(ans, (p[i]-p[i+1]+a[i]-a[i+1])/2+a[i+1]);
        }
    }
    cout << ans << endl;
    return 0;
}

G.拜登打牌

考察算法:
二维偏序,树状数组
思路:
本题是一个典型的二维偏序题目,如果有选手不明白二维偏序,可以参考一下这篇文章——二维偏序介绍。简单来说,二维偏序就是定义了一个二维的“小于”关系,因为要把每个数分级,所以自然而然的我们考虑排序,问题的关键就在于如何排序。这里我们按照a的大小做为第一关键字,b的大小做为第二关键字(即a相同时,b小的在前面,否则a小的在前面)排序。这样排序的目的就是把所有可能小于当前点的点都在当前点前面被处理。
之后我们遍历排序之后数组的每项的b值,显然比第 i i i 项小的数量等于 i − 1 i-1 i1项中b比第 i i i 项的b小的项的数量(因为第 i i i 项的a一定大于前 i − 1 i-1 i1项)
举个例子,比如
( 1 , 1 ) , ( 2 , 3 ) , ( 2 , 7 ) , ( 3 , 8 ) ( 4 , 4 ) , ( 4 , 6 ) , ( 5 , 1 ) (1, 1),(2,3),(2,7),(3, 8)(4,4),(4,6),(5,1) (1,1),(2,3),(2,7),(3,8)(4,4),(4,6),(5,1)
对于 ( 4 , 4 ) (4,4) (4,4),它后面的两项一定比它大,而它前面的 ( 1 , 1 ) , ( 2 , 3 ) (1, 1),(2,3) (1,1),(2,3)比它小,而 ( 2 , 7 ) , ( 3 , 8 ) (2,7),(3,8) (2,7),(3,8)比它大,所以它是三级手牌。
现在我们的问题就变成了对每个数,找到已经遍历的数字中比它的b小的数的数量,然后再加上1,即是它的级数。我们可以建立一个新数组 c n t cnt cnt,每遍历一个b,就把这个数组第b项加1,比如上面的例子中,前五项 c n t [ 1 ] = c n t [ 3 ] = c n t [ 4 ] = c n t [ 7 ] = c n t [ 8 ] = 1 cnt[1]=cnt[3]=cnt[4]=cnt[7]=cnt[8]=1 cnt[1]=cnt[3]=cnt[4]=cnt[7]=cnt[8]=1,其余项为0,所以比4小(或等于)的数量即为 c n t [ 0 ] + c n t [ 1 ] + c n t [ 2 ] + c n t [ 3 ] + c n t [ 4 ] = 3 cnt[0]+cnt[1]+cnt[2]+cnt[3]+cnt[4]=3 cnt[0]+cnt[1]+cnt[2]+cnt[3]+cnt[4]=3, 但这相当于我们对每一项都需要 O ( n ) 遍 历 O(n)遍历 O(n),总时间复杂度为 O ( n 2 ) O(n^2) O(n2),能不能优化呢?
树状数组可以很好的解决这个问题。树状数组也是一种十分优美的数据结构,可以在 O ( l o g n ) O(logn) O(logn)的时间复杂度对前缀和进行查询or对单点进行修改,也就是说,查询 c n t [ 0 ] + c n t [ 1 ] + . . c n t [ n ] cnt[0]+cnt[1]+..cnt[n] cnt[0]+cnt[1]+..cnt[n] or c n t [ b i ] cnt[b_i] cnt[bi]++ 的时间复杂度从 O ( n ) O(n) O(n) 优化到 O ( l o g n ) O(logn) O(logn), 总时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),强烈建议不懂的选手学习一下。或许选手不需要知道它的数学原理,只需要会用就可以了。
代码中有一个小细节,就是压入树状数组时要把b的值+1,因为b可能取0,也就是可能出现 c n t [ 0 ] cnt[0] cnt[0] 而使用树状数组的话,可以理解为下标必须至少为1,所以就把所有数都+1。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e4 + 10;
const int maxm = 5e4 + 10;
int n;
int C[maxm], ans[maxn];
//lowbit(),query(),update()为树状数组的三个函数
int lowbit(int x) {
    return x & (-x);
}
int query(int x) {    //前x组的和
    int ret = 0;
    while(x > 0) {
        ret += C[x];
        x -= lowbit(x);
    }
    return ret;
}
void update(int x, int d) {    //第x组加d
    while(x <= maxm) {
        C[x] += d;
        x += lowbit(x);
    }
}
struct node {
    int a, b;
    bool operator < (const node &x) const {	//排序,按照a为第一关键字
        if(a == x.a) return b < x.b;		//b为第二关键字,从小到大
        return a < x.a;
    }
}p[maxn];
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> p[i].a >> p[i].b;
    sort(p + 1, p + n + 1);
    for(int i = 1; i <= n; i++) {
        update(p[i].b + 1, 1);
        ans[query(p[i].b + 1)]++;
    }
    for(int i = 1; i <= n; i++) cout << ans[i] << '\n';
    cout << '\n';
    return 0;
}

Note:
本题保证了没有重复卡牌,如果有重复卡牌的话这份代码还仍然正确吗?感兴趣的选手可以思考一下。我都这么问了当然是不正确的,然而只需要稍加修改即可

H. An Easy Problem

考察算法:
组合数学,乘法逆元
思路:
1 , 2 , . . . , n − 1 , n 1,2,...,n-1,n 1,2,...,n1,n 的全排列一共有 n ! n! n! 种,每种全排列对应的对数一共有 C ( n , 2 ) = n ∗ ( n − 1 ) / 2 C(n, 2) = n * (n - 1) / 2 C(n,2)=n(n1)/2 种。对于其中的每一对数,逆序对和“正序对”出现的次数显然是相等的,故除以 2 2 2即可得到最终答案。 注意取模即可。最终答案为:
a n s = n ! ∗ n ∗ ( n − 1 ) / 4 ans = n! * n * (n - 1) / 4 ans=n!n(n1)/4
这个方法可能听起来有点抽象,举个例子,比如1-5的全排列,显然形如{x,x,3,2,x}的一共有 3 ! = 6 3!= 6 3=6 种,同时,形如{x,x,2,3,x}的也同样有6种,而前者相当于3,2处在第三、四个位置上贡献了6个逆序对,后者则为0个。容易得到对于任意的两个数都是类似的,于是得到了上述公式。

时间复杂度:预处理 O ( n ) O(n) O(n),对每个询问 O ( 1 ) O(1) O(1)回答

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define LL long long
const int maxn = 1e6 + 10;
const int P = 998244353; 
LL power(LL a, LL b) {
    LL ans = 1 % P;
    for (; b; b >>= 1) {
        if (b & 1)  ans = ans * a % P;
        a = a * a % P;
    }
    return ans;
}
LL inv(int a) {	//费马小定理求逆元
    return power(a, P - 2);
}
int fac[maxn];
void init() {	//预处理阶乘
    fac[0] = 1;
    for(int i = 1; i < maxn; i++) {
        fac[i] = fac[i-1] * i % P;
    }
}
int z = inv(4);
void solve() {
    int n;
    cin >> n;
    int ans = fac[n];
    ans = ans * n % P * (n - 1) % P * z % P;
    cout << ans << '\n';
}
signed main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    init();
    int T; cin >> T; while(T--)
        solve();
    return 0;
}

I.求求你啦,帮帮可莉吧

考察算法:
进制与整除,同余
灵感来源于一场巴西的Regional,Creating Multiples,与本题唯一区别就是原题 m o d = k + 1 mod=k+1 mod=k+1,而本题为 m o d = k − 1 mod = k - 1 mod=k1,事实上本题是该题的简化版,但方法类似,只是规律更容易发现,感兴趣的选手可以尝试一下原题。
思路:
经过观察得, k i ≡ 1 ( m o d ( k − 1 ) ) ∀ k , i ∈ N ( k > 1 , i > 0 ) k^{i}≡ 1 (mod (k-1))\quad\forall k, i\in\mathbb N(k > 1,i > 0) ki1(mod(k1))k,iN(k>1,i>0)
我们知道,对于一个 k k k 进制数,它的第 i i i 位(从低向高) a i a_i ai可以表示成 a i ∗ k i − 1 a_i*k^{i-1} aiki1,所以我们得到 a i ∗ k i − 1 ≡ a i ( m o d ( k − 1 ) ) ∀ k , i ∈ N ( k > 1 , i > 1 ) a_i * k^{i-1}≡ a_i (mod (k-1))\quad\forall k, i\in\mathbb N(k > 1, i > 1) aiki1ai(mod(k1))k,iN(k>1,i>1)
也就是说,设 K K K进制数 B B B a 1 , a 2 , . . . a n a_1,a_2,...a_n a1,a2,...an,则
B ≡ Σ i = 1 n a i ( m o d ( k − 1 ) ) B ≡ Σ_{i=1}^na_i (mod (k - 1)) BΣi=1nai(mod(k1))
所以我们可以先通过对 a i a_i ai求和,计算出它模 k − 1 k-1 k1的余数 s s s,然后从高位向低位遍历,找到第一个不小于 s s s的数,将这一位减去 s s s,得到的即为模 k − 1 k-1 k1余0的数,显然这是最小的。(不可能减去 2 ∗ s 2*s 2s,虽然后者如果可行的话更小,感兴趣的选手可以思考一下为什么)
时间复杂度 O ( l ) O(l) O(l) l l l为数字的位数。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int k, l;
int a[maxn];
int main() {
    scanf("%d%d", &k, &l);
    int yu = 0;
    int mod = k - 1;
    for(int i = l; i >= 1; i--) {
        scanf("%d", &a[i]);
        yu = (yu + a[i]) % mod;
    }
    if(!yu)
        printf("0\n");
    else {
        for(int i = l; i >= 1; i--) {
            if(a[i] < yu) continue;
            printf("%d %d\n", l - i + 1, a[i] - yu);
            return 0;
        }
        printf("-1\n");
    }
    return 0;
}

J.嘉然小姐的魔法通道

考察算法:
并查集,二分答案
思路:
如果大家没有学习过并查集,建议先去了解一下并查集——一种非常经典的数据结构,本题只需要用到并查集的两个最基本的应用——查找两个元素是否处在一个集合&合并两个元素所在的两个集合。并查集讲解

本题改编自P3958 [NOIP2017 提高组] 奶酪,建议大家先把原题搞懂,洛谷的题解讲的十分到位,鄙人就不多赘述了。在原题的基础上,我们引入了“每隔1秒,魔法球的半径都会增长1”这一条件,所以考虑通过二分时间的方法来解决,具体题解见代码注释。
如果大家对二分答案部分有疑问,强烈建议学会这一方法。二分答案讲解
时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e3 + 10;
const int INF = 0x7f7f7f7f;
int n, e;
int x[maxn], y[maxn], r[maxn];
int fa[maxn];
int find(int x) {   //并查集
    if(x == fa[x])
        return x;
    return fa[x] = find(fa[x]);
}
void merge(int i, int j) { //并查集
    int fx = find(i);
    int fy = find(j);
    fa[fx] = fy;
}
bool judge(int i, int j, int t) {//计算t秒后i, j两点是否相互可达
    double dis = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); //距离
    double now = r[i] + r[j] + (t << 1); //半径和
    if(now >= dis)
        return true;    //两点可达
    return false;   //两点不可达
}
bool ok(int t) {
    for(int i = 0; i <= n + 1; i++) {   //每次均要初始化父节点
        fa[i] = i;
    }
    for(int i = 1; i <= n; i++) {
    	if(abs(x[i]-e) <= t + r[i])  //如果第i个点可以到达终点,则合并
            merge(i, n + 1);
        if(abs(x[i]-0) <= t + r[i]) //如果第i个点可以到达起点,则合并
            merge(i, 0);
        for(int j = 1; j < i; j++)
            if(judge(i, j, t))  //如果第i个点和第j个点可以相互到达,则合并
                merge(i, j);
    }
    int fx = find(0);
    int fy = find(n + 1);
    if(fx == fy)
        return true;    //如果起点和终点在同一个集合里,即相互可达,return true
    return false;      //否则return false
}
signed main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> e;
    for(int i = 0; i <= n + 1; i++)
        fa[i] = i;
    for(int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i] >> r[i];
    }
    x[0] = 0; y[0] = 0; r[0] = 0;       //起点
    x[n + 1] = e; y[n + 1] = 0; r[n + 1] = 0; //终点
    int L = 0, R = INF; //实际上限为sqrt(y^2 + max(e^2,1e9-e^2)),方便起见直接设为INF。
    int ans = INF;
    while(L <= R) { //二分答案
        int mid = (L + R) >> 1;
        if(ok(mid)) {
            R = mid - 1;
            ans = mid;
        }
        else
            L = mid + 1;
    }
    cout << ans << '\n';
    return 0;
}

K.嘉然小姐的魔法日记

考察算法:
主席树(可持久化线段树)
思路:
出题人做法:对原序列建主席树,由于年份不是连续的,离散化处理一下即可。对主席树有兴趣的选手如果还不会的话可以参考主席树模板中的题解,鄙人在此就不解释了。
更简单的做法:
比赛时发现大部分AC代码都是用离线做法,这波出题人表示也没有想到
下面附上队友写的主席树std做为参考。

代码

#include<iostream>
#define maxn 400500
#define mid (l+r)/2
using namespace std;
typedef struct hjtree{
	int l,r;
	int val;
}hjtree;
int n,m;
int a[maxn];
int cnt=0,root[2*maxn];
int tmp=0;
int searcher[2*maxn]; 
hjtree t[(2*maxn)<<6];
void init(int &o,int l,int r){
	o=++cnt;
	if(l==r){
		t[o].val=a[l];
		return;
	}
	init(t[o].l,l,mid);
	init(t[o].r,mid+1,r);
	return;
}
void build(int &o,int pre,int l,int r,int pos,int val){
	o=++cnt;
	t[o]=t[pre];
	if(l==r){
		t[o].val=val;
		return;
	}
	if(pos<=mid)build(t[o].l,t[pre].l,l,mid,pos,val);
	else build(t[o].r,t[pre].r,mid+1,r,pos,val);
}

int query(int o,int l,int r,int pos){
	if(l==r)return t[o].val;
	if(pos<=mid)return query(t[o].l,l,mid,pos);
	else return query(t[o].r,mid+1,r,pos);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	cin>>n>>m;
	
	for(int i=1;i<=n;i++)cin>>a[i];
	
	init(root[0],1,n);

	for(int i=1;i<=m;i++)
	{
		int op;
		cin>>op;
		if(op==0){
			int y,p1,p2;
			cin>>y>>p1>>p2;
			int val1=query(root[tmp],1,n,p1),val2=query(root[tmp],1,n,p2);
			tmp++;
			searcher[tmp]=y;
			build(root[tmp],root[tmp-1],1,n,p1,val2);
			tmp++;
			searcher[tmp]=y;
			build(root[tmp],root[tmp-1],1,n,p2,val1);
		}else if(op==1){
			int y,p;
			cin>>y>>p;
			int version=(lower_bound(searcher,searcher+tmp+1,y)-searcher);
			if(version!=0){
				if(searcher[version]>y)version--;
				else version++;
			}
			cout<<query(root[version],1,n,p)<<"\n";
		}
	}
	return 0;
}

L.七海の新春旅行

考察算法:
动态规划,背包问题
思路:
思路比较清晰,由于背包尽量填满,于是初始时背包只有容量为0时能用,于是可以设背包的其它值为-1,然后正常跑完全背包求出每个行李箱容量下的最大价值,然后进行一次01背包,选出重量不超过 m m m 的情况下产生的最大价值。但这题的关键在于由于 n n n 的范围很大,在若直接进行背包会超时,注意到最多只存在m个本质相同的行李箱,于是可以按照多重背包的思路进行二进制优化或单调队列优化,即可解决此题。
时间复杂度 O ( m ∗ k + m ∗ m ∗ l o g n ) O(m*k+m*m*logn) Omk+mmlogn
本来想把 g i 和 h i g_i和h_i gihi的范围都设为 m m m 的范围也就是 ≤ 2000 \leq 2000 2000,然而出题人说由于数据太大python跑不出来于是定为了300,好像又被选手卡过去了。

代码

#include<iostream>
using namespace std;
int m,n,k;
int g[200500];
int h[2050];
long long v[2050];
int mp[2550],cnt[2550];
long long f1[2550],f2[2550];
int nw[200500];
long long nv[200500];
int tmp=0;
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>m>>n>>k;
	for(int i=1;i<=n;i++)cin>>g[i];
	for(int i=1;i<=k;i++)cin>>h[i]>>v[i];
	for(int i=1;i<=m;i++)f1[i]=-1;
	f1[0]=0;
	for(int i=1;i<=k;i++)
	{
		for(int j=h[i];j<=m;j++)
		{
			if(f1[j-h[i]]!=-1)f1[j]=max(f1[j],f1[j-h[i]]+v[i]);
		}
	}
	for(int i=0;i<=m;i++)mp[i]=i;
	for(int i=1;i<=m;i++)
	{
		if(f1[i]==-1)mp[i]=mp[i-1];
	}
	for(int i=1;i<=n;i++)
	{
		if(g[i]<=m)cnt[mp[g[i]]]++;
	}
	for(int i=1;i<=m;i++)
	{
		if(f1[i]==-1)continue;
		for(int j=1;j<=cnt[i];j<<=1)
		{
			nw[++tmp]=j*i;
			nv[tmp]=j*f1[i];
			cnt[i]-=j;
		}
		if(cnt[i]){
			nw[++tmp]=cnt[i]*i;
			nv[tmp]=cnt[i]*f1[i];
		}
	}
	for(int i=1;i<=tmp;i++)
	{
		for(int j=m;j>=nw[i];j--)
		{
			f2[j]=max(f2[j],f2[j-nw[i]]+nv[i]);
		}
	}
	cout<<f2[m];
	return 0;
}

M.嘉然小姐的新春礼物

**考察算法:**状压dp
思路:
注意到 n n n 的范围在16以内,容易想到使用状压dp解决此题,首先状压枚举礼物,求出每组礼物被一个套环圈住的最小圆,由于 m m m 比较大,容易想到二分查找最小的大于所求圆的圆环,但是数据不一定是递增的,即更大的圆环的价值可能更小,因此首先预处理,贪心地舍弃不符合规则的圆环,然后就可以二分求解了,这样得到若干个礼物被一个圆环套住的最小价值,然后就可以枚举子集状压dp求解最终结果了,时间复杂度 O ( 2 n ∗ l o g m + 3 n ) O(2^n*logm+3^n) O2nlogm+3n
下面给出队友写的std

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
const int maxn = 1e9+7;
using namespace std;
struct vec
{
	double x, y;
	vec (const double& x0 = 0, const double& y0 = 0) : x(x0), y(y0) {}
	vec operator + (const vec& t) const {return vec(x+t.x, y+t.y);}
	vec operator - (const vec& t) const {return vec(x-t.x, y-t.y);}
	vec operator * (const double& t) const {return vec(x*t, y*t);}
	vec operator / (const double& t) const {return vec(x/t, y/t);}
	const double len2 () const {return x*x + y*y;}
	const double len () const {return sqrt(len2());}
	vec norm() const {return *this/len();}
	vec rotate_90_c () {return vec(y, -x);}
};
typedef struct loop{
	int rad,p;
}loop;
int n,m;
vec potn[25];
vec pot[25];
loop ring1[200500];
int v[200500];
loop ring2[200500];
int tmp=0;
int mp[200500];
int f[(1<<20)];
int cmp(loop l1,loop l2){
	if(l1.rad==l2.rad)return l1.p>l2.p;
	else return l1.rad<l2.rad; 
}
double dot(const vec& a, const vec& b) {return a.x*b.x + a.y*b.y;}
double crs(const vec& a, const vec& b) {return a.x*b.y - a.y*b.x;}
vec lin_lin_int(const vec& p0, const vec& v0, const vec& p1, const vec& v1)
{
	double t = crs(p1-p0, v1) / crs(v0, v1);
	return p0 + v0 * t;
}
vec circle(const vec& a, const vec& b, const vec& c)
{
	return lin_lin_int((a+b)/2, (b-a).rotate_90_c(), (a+c)/2, (c-a).rotate_90_c());
}
double getcircle(int siz){
	random_shuffle(pot+1, pot+siz+1);
	vec o;
	double r2 = 0;
	for(int i=1; i<=siz; i++)
	{
		if((pot[i]-o).len2() > r2)
		{
			o = pot[i], r2 = 0;
			for(int j=1; j<i; j++)
			{
				if((pot[j]-o).len2() > r2)
				{
					o = (pot[i]+pot[j])/2, r2 = (pot[j]-o).len2();
					for(int k=1; k<j; k++)
					{
						if((pot[k]-o).len2() > r2)
						{
							o = circle(pot[i], pot[j], pot[k]), r2 = (pot[k]-o).len2();
						}
					}
				}
			}
		}
	}
	return sqrt(r2);
} 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>potn[i].x>>potn[i].y;
	for(int i=1;i<=m;i++)cin>>ring1[i].rad>>ring1[i].p;
	sort(ring1+1,ring1+m+1,cmp);
	int minl=maxn;
	for(int i=m;i>=1;i--)
	{
		if(ring1[i].p>=minl)v[i]=1;
		minl=min(minl,ring1[i].p);
	}
	for(int i=1;i<=m;i++)
	{
		if(!v[i])ring2[++tmp]=ring1[i];
	}
	tmp++;
	ring2[tmp].rad=maxn,ring2[tmp].p=maxn;
	for(int i=1;i<=tmp;i++)mp[i]=ring2[i].rad;
	for(int i=1;i<=(1<<n)-1;i++)f[i]=maxn;
	for(int i=1;i<=(1<<n)-1;i++)
	{
		int siz=0;
		int h=i,cnt=1;
		while(h){
			if((h&1)){
				pot[++siz]=potn[cnt];
			}
			cnt++;
			h>>=1;
		} 
		double rr=getcircle(siz);
		f[i]=ring2[lower_bound(mp+1,mp+tmp+1,rr)-mp].p;
	}
	for(int i=1;i<=(1<<n)-1;i++)
	{
		for(int j=i;j!=0;j=((j-1)&i))
		{
			f[i]=min(f[i],f[i-j]+f[j]);
		}
	}
	cout<<f[(1<<n)-1];
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值