义乌集训7.21 contest 14题解

2021.7.19 Contest 题解

T1:

Description:

​ 小A来到了一个商店,商店内有 \(n\) 件物品,每件物品有一个售价 \(A_i\) ,小A是一个奸商,因此他能够把每件物品以 \(B_i\) 的价格卖出。

​ 而由于小A背包空间有限,他只能买走恰好一件商品。而小A每次购买之后,小B将有可能将小A的物品摧毁,此时小A必须继续购买,并且商店不会返还购买这件物品的钱。小B至多可以摧毁 \(m\) 件物品。

​ 小A的目标是最大化自己的收益。

​ 小B的目标是最小化小A获得的收益,即卖出的商品所得减去购买所有物品的总花费。

​ 注意到任何时刻小B都可以选择不将小A手中的物品摧毁,这样小A就必须立刻带着这件物品离开商店。

Input:

​ 第一行一个数 \(T\) ,代表数据组数

​ 每组第一行两个数 \(n,m\) ,分别表示物品数目和小B可摧毁的物品数量。

​ 之后 \(m\) 行,每行两个数 \(A_i,B_i\)

Output:

​ 对于每组数据,输出一个数表示小A最终能获得的收益,由于小A可以直接走出商店(什么都不买),这个收益显然是非负的。

Sample1 Input:

2
3 1
1 3
10 30
10 30
3 0
1 3
10 30
100 130

Sample1 Output:

10
30

Hint:

​ 对于 \(100\%\) 的数据,\(0 \leq m <n\le10^5,1 \leq A_i,B_i \leq 10^9,m\leq 20,\sum{n} \leq 3\times 10^5\)

题目分析:

​ 考虑二分一个答案 \(mid\),也就是说一开始满足 \(B_i-A_i \geq mid\) 的物品小A会去买,小B也会去销毁;到了第二次时,需要满足 \(B_j-A_j \geq mid+A_i\) 小A才会去买,小B也会去销毁……一直到小B无法销毁同时仍有满足要求的 \(B_k-A_k\)

​ 我们考虑贪心,若 \(B_i-A_i \geq mid\)\(B_j-A_j \geq mid\),肯定会先购买 \(B\) 较小的,然后再购买 \(B\) 较大的。证明显然,如果先选 \(j\),则再选 \(i\) 需要满足 \(B_i-A_i \geq mid+A_j\) ;而先选 \(i\),再选 \(j\) 需要满足 \(B_j-A_j \geq mid+A_i\) ,移项可得,一个是 \(B_i \geq mid+A_j+A_i\),另一个是 \(B_j \geq mid+A_i+A_j\) ,那么显然先拿 \(B\) 较小的肯定更优。

​ 在确定了选择顺序之后我们就可以DP了,设 \(f_{i,j}\) 表示枚举到第 \(i\) 个物品,小B销毁了 \(j\) 次,此时所需的 \(B_i-A_i\) 的最小值,然后我们就能在 \(O(nmlogV)\) 的复杂度内解决这道题。

​ 当然,不用二分直接DP的做法也行,留给读者思考。

代码如下(马蜂很丑,不喜勿喷)——

//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,fma")
//#pragma GCC optimize("unroll-loops")
#include<bits/stdc++.h>
#define N 100005
#define LL long long
#define inf 2147483647
using namespace std;
int T,n,m,Max,rk[N],a[N],b[N],f[25]; inline bool cmp(int x,int y){if(b[x]^b[y]) return b[x]<b[y];return a[x]<a[y];} inline bool check(int x){
	f[0]=x;for(register int i=1;i<=m;i++) f[i]=inf;for(register int i=1;i<=n;i++) 
	for(register int j=m;j;j--) if(b[rk[i]]-a[rk[i]]>=f[j-1]) f[j]=min(f[j],a[rk[i]]+f[j-1]);return f[m]^inf;
//	int now=x,K=m;for(register int i=1;i<=n;i++) if(b[rk[i]]-a[rk[i]]>=now) if(!K) return 1;else K--,now+=a[rk[i]];return 0;
} 
//inline bool cmp2(int x,int y){if(a[x]^a[y]) return a[x]<a[y];return b[x]<b[y];}
//inline bool cmp3(int x,int y){return a[x]+b[x]<a[y]+b[y];}
struct FastIO{
	static const int S=1048576;
	char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
	inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('\n');}
	FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='\n');return *this;}
	template<typename T>FastIO& operator >> (T&ret){
		ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
		while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
	}
	FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='\n'){*(s+Len)=ch;Len++;ch=nc();}}
	template<typename T>FastIO& operator << (T x){
		if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
		while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
	}
	FastIO& operator << (char ch){pc(ch);return *this;}
	FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
//	freopen("a.in","r",stdin);freopen("a.ans","w",stdout);
	fin>>T;while(T--){
		fin>>n>>m,m++,Max=0;for(register int i=1;i<=n;i++) fin>>a[i]>>b[i],Max=max(Max,b[i]-a[i]),rk[i]=i;
		sort(rk+1,rk+n+1,cmp);int l=0,r=Max,ans=0;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}fout<<ans<<'\n';
//		sort(rk+1,rk+n+1,cmp2);l=ans+1,r=Max;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}
//		sort(rk+1,rk+n+1,cmp3);l=ans+1,r=Max;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}fout<<ans<<'\n';
	}
	return 0;
}
//21224751

T2:

Description:

​ 小A有一张无向图 \(G(x)\),图中每条边的权值可以表达为 \(ax+b\),现在小A想知道对于在 \([l,r]\) 之间的每一个 \(x\),使得这张图的最小生成树最大的那一个 \(x\),所对应的最小生成树的权值是多少。

Input:

​ 第一行两个数 \(n,m\),分别表示点数和边数。

​ 之后 \(m\) 行,每行四个数 \(x,y,a,b\),表示一条边的端点和边权 \((ax+b)\)

​ 最后一行两个整数 \(l,r\)

Output:

​ 一个实数(保留三位小数),代表最小生成树的最大值。

Sample1 Input:

3 3
1 2 1 10
2 3 10 10
3 1 100 10
-23 23

Sample1 Output:

273.000

Hint:

对于 \(100\%\) 的数据,\(n<m\le10^5\)\(-10^9\le l\le r\le10^9\)\(|a_i|,|b_i|\le 10^5\),保证图是联通的。

对于 \(20\%\) 的数据,\(r-l\le10\)

对于另外 \(10\%\) 的数据,\(a_i=0\)

对于另外 \(30\%\) 的数据,\(n,m\le10^3\)\(|a_i|\le1\)

题目分析:

​ 对于图中的每一棵生成树,它的权值是关于 \(x\) 的一次函数,那么原问题实际上是求对于每个 \(x\) 在若干个函数中取最小值从而得到一个新的上凸函数,我们要求这个函数在 \([l,r]\) 中的最大值,这个问题显然可以三分,然后这道题就结束了。

​ 具体实现的时候需要注意精度、边界以及常数等一系列问题。

代码如下(马蜂很丑,不喜勿喷)——

#include<bits/stdc++.h>
#define N 100005
#define DB double
#define LL long long
#define eps 1e-7
using namespace std;
int n,m,X[N],Y[N],A[N],B[N],f[N];double ans,l,r;
struct node{int x,y;DB v;}p[N];inline bool cmp(const node x,const node y){return x.v<y.v;} inline int gf(int x){return f[x]==x?x:f[x]=gf(f[x]);} inline DB get(DB xx){
	for(register int i=1;i<=n;i++) f[i]=i;for(register int i=1;i<=m;i++) p[i].x=X[i],p[i].y=Y[i],p[i].v=1.0*A[i]*xx+1.0*B[i];sort(p+1,p+m+1,cmp);
	DB res=0;for(register int i=1;i<=m;i++){int x=p[i].x,y=p[i].y,fx=gf(x),fy=gf(y);if(fx==fy) continue;res+=p[i].v,f[fx]=fy;}return res;
}
struct FastIO{
	static const int S=1048576;
	char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
	inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('\n');}
	FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='\n');return *this;}
	template<typename T>FastIO& operator >> (T&ret){
		ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
		while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
	}
	FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='\n'){*(s+Len)=ch;Len++;ch=nc();}}
	template<typename T>FastIO& operator << (T x){
		if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
		while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
	}
	FastIO& operator << (char ch){pc(ch);return *this;}
	FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
	fin>>n>>m;for(register int i=1;i<=m;i++) fin>>X[i]>>Y[i]>>A[i]>>B[i];fin>>l>>r;ans=-214748364777777777;while(r-l>eps)
	{DB mid1=(2.0*l+r)/3.0,mid2=(l+2.0*r)/3.0;if(get(mid1)<=get(mid2)) l=mid1;else r=mid2;}ans=max(ans,max(get(l),get(r)));printf("%0.3lf\n",ans);return 0;
}

T3:

Description:

​ 在计算机编译原理中,我们可以了解到上下文无关文法(CFG)这一概 念,我们可以用几个集合来代表一个文法,如语法 \(G = (V,S,P,T)\)\(V\) 代表文法词汇表,其中 \(S\) 代表开始符号的集合,\(P\) 代表生成式的集合,\(T\) 代表终止符号的集合。

​ CFG 的工作流程可以这样描述:对于一个由词汇表 \(T\) 中的词汇构成的句子,可以按照生成式进行展开。如当 \(V = \{a,b,c,0\}\),开始符集 \(S = \{a\}\),结束符集 \(T = \{0\}\),生成式集有如下

\(P = \{a \to bc,b \to cc,c \to 0,c \to b\}\)

​ 当我们输入字符串 \(abb\)b 时,文法分析检测到开始符,对应生成式可以推导出

\(abb \to bcbb \to ccccccc \to 0000000\)

​ 此时,通过生成式展开的式子只具有终止符,我们就认定文法分析结束,当然,我们的推导也可以为

\(abb \to bcbb \to ccccccc \to bbbbbbb \to ...\)

​ 这样无限推导下去,可以得到任意长度的式子,但是当我们输入 \(0a0\) 时,发现句首句尾的终止符不会发生变化,不能由终止符推导出其他式子,我们称其为上下文无关文法,也就是说我们定义的生成式 \(s_1 \to s_2\) 中,\(s_1 \notin T\)

​ 我们给出此题的语法 \(G = (V,S,P,T)\),其中 \(V = \{A-Z,0,1\}\),即 \(26\) 个大写英文字母与 \(0, 1\) 两个数字;\(T=\{0,1,\}\),即终止符为 \(0, 1\) 和空(长度为 \(0\) 的字符串)三种情况。我们认定,当推导出的式子中只剩 \(0, 1\) 或什么也没有时,文法分析结束。其开始符 \(S=\{S\}\) ,即开始符只有 \(S\) 这个字母;\(P\) 包含的 \(n\) 个生成式则由测试数据给出。

​ 现在,Archies只输入了开始符 \(\large S\),他想知道在文法分析结束后,由生成式推导出的式子(字符串)的前 \(k\) 个字符有多少种不同的可能,特别的,当式子的长度不超过 \(k\) 时,也视为一种合法的情况,请你解决Archies的疑问,并输出这些长度不超过 \(k\) 的前缀。

Input:

​ 第一行两个整数 \(n,k\),其意义见题目描述。

​ 接下来 \(n\) 行,每行一个生成式,其形式为 \(s_1 \to s_2\) ,保证 \(s_1\) 不为 \(0,1\) 或空,保证 \(s_2\) 的长度不会超过 \(10\)

Output:

​ 输出第一行一个整数 \(c\),代表所有合法情况的数量。

​ 之后 \(c\) 行,每行一个长度不超过 \(k\) 的字符串,你可以按任意顺序输出这些字符串。你可以输出字符串 eps 代表一个空串。

Sample1 Input:

2 3
S->0S1S
S->

Sample1 Output:

5
eps
01
000
001
010

Hint:

data range:

对于 \(100\%\) 的数据,\(n \le 200,k \le 7\)

题目分析:

​ 考虑每个字符能变成什么,直接模拟这一个过程。。。

标程代码如下(马蜂很丑,不喜勿喷)——

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>
#include <stack>
#include <queue>
#include <set>
#include <unordered_set>
#include <map>
typedef long long LL;
using namespace std;
const int N = 26;

inline void judge()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
}

int n , K;
char str[N];
vector<string> trans[N];
vector<int> prefix[N];
bool vis[26][1 << 8];
 
bool isrow(char *s) {
    while (*s) {
        if (!isdigit(*s ++))
            return 0;        
    }
    return 1;
}
 
string itos(int x) {
    string s = "";
    while (x > 1) {
        s += (x & 1) + '0';
        x >>= 1;
    }
    reverse(s.begin() , s.end());
    return s;
}
int Stoi(string s) {
    int x = 1;
    for (int i = 0 ; i < s.size() && i < K ; ++ i)
        x = x << 1 | s[i] - '0';
    return x;
}
 
int cat(int A , int B) {
    int begin = -1;	
    for (int i = K ; i >= 0 ; -- i)
        if (B >> i & 1) {
            begin = i;
            break;
        }
    while (begin > 0) {
        int tmp = A << 1 | (B >> (-- begin) & 1);
        if (tmp >= 1 << K + 1)
            break;
        A = tmp;
    }
    return A;
    string C = itos(A) + itos(B);
    if (C.size() > K)
        C = C.substr(0 , K);
    return Stoi(C);
}
 
int main() {
	judge();
    scanf("%d%d\n" , &n , &K);
    //puts("233");
    for (int i = 0 ; i < n ; ++ i) {
        scanf("%s" , str);
        int c = *str - 'A';
        trans[c].push_back(str + 3);
        if (isrow(str + 3)) {
            int num = Stoi(str + 3);
            if (!vis[c][num]) {
                vis[c][num] = 1;
                prefix[c].push_back(num);
            }
        }
    }
    int mask = 1 << K + 1;
    while (1) {
        bool changed = 0;
        for (int i = 0 ; i < 26 ; ++ i) {
            for (auto &t : trans[i]) {
                static bool Hash[2][1 << 8];
                int cur = 0 , nxt = 1;
                memset(Hash[cur] , 0 , sizeof(Hash[cur]));
                Hash[cur][1] = 1;
                for (auto &ch : t) {
                    memset(Hash[nxt] , 0 , sizeof(Hash[nxt]));
                    for (int j = 1 ; j < mask ; ++ j) {
                        if (!Hash[cur][j])
                            continue;
                        if (ch == '0') {
                            Hash[nxt][cat(j , 2)] = 1;
                        } else if (ch == '1') {
                            Hash[nxt][cat(j , 3)] = 1;
                        } else {
                            for (auto &s : prefix[ch - 'A'])
                                Hash[nxt][cat(j , s)] = 1;
                        }
                    }
                    swap(cur , nxt);
                }                
                for (int s = 1 ; s < mask ; ++ s) {
                    //cout << s << endl;
                    if (Hash[cur][s] && !vis[i][s]) {
                        changed = 1;
                        vis[i][s] = 1;
                        prefix[i].push_back(s);
                    }
                }
            }
        }
        if (!changed)
            break;
    }
    cout << prefix['S' - 'A'].size() << endl;
    for (auto &s : prefix['S' - 'A']) {
        if (s == 1)
            cout << "eps" << endl;
        else
            cout << itos(s) << endl;
    }    
    return 0;
}

T4:

Description:

​ Solo生成了一个一条长度为 \(n\) 的链,链上的点标号 \(1\)\(n\),其中 \(i\) 号点和 \(i + 1\) 号点之间的边的权值为 \(A_i\)

​ 现在Solo决定从中删除 \(k\) 条两两之间没有公共点的子链(长度可以为 \(0\),即一个单独的节点),使得这 \(k\) 条链上涵盖的边的边权和最大。

Input:

​ 第一行两个整数 \(n,k\),其意义见题目描述。

​ 第二行 \(n - 1\) 个整数,其中第 \(i\) 个整数代表第 \(i\) 条边的 \(A_i\)

Output:

​ 第一行一个整数代表最大值;

​ 接下来 \(k\) 行每行两个数,表示最大值对应的方案中选出的条链的起点和终点。

Sample1 Input:

9 4
-10 2 1 3 6 -2 17 1

Sample1 Output:

29
1 1
2 6
7 8
9 9

Hint:

对于 \(100\%\) 的数据,\(|Ai| \lt 10^5,k \le n \le 100000\)

题目分析:

​ 正解应该是WQS二分,然而博主没写出来,暂时先咕咕了QAQ(也许会永远咕下去

代码如下(马蜂很丑,不喜勿喷)——

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N=100005;
const ll inf=1e12;
ll n,i,j,K,p,l,t,a[N],s[N];
ll f[N][2],L[N][2],R[N][2];
ll tot,ansl[N],ansr[N],ans;
inline void read(ll &x)
{
	x=0; ll ff=1; char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') ff=-1,ch=getchar();
	while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	x*=ff;
}
void update(ll x,ll v)
{
	f[x][0]=f[x-1][0];
	f[x][0]=max(f[x][0],f[x-1][0]-v);
	f[x][0]=max(f[x][0],f[x-1][1]+a[x-1]);
	f[x][1]=f[x-1][0]-v;
	f[x][1]=max(f[x][1],f[x-1][1]+a[x-1]);
	L[x][0]=inf; R[x][0]=-1;
	L[x][1]=inf; R[x][1]=-1;
	
	if (f[x][0]==f[x-1][0]) L[x][0]=min(L[x][0],L[x-1][0]),R[x][0]=max(R[x][0],R[x-1][0]);
	if (f[x][0]==f[x-1][1]+a[x-1]) L[x][0]=min(L[x][0],L[x-1][1]),R[x][0]=max(R[x][0],R[x-1][1]);
	if (f[x][0]==f[x-1][0]-v) L[x][0]=min(L[x][0],L[x-1][0]+1),R[x][0]=max(R[x][0],R[x-1][0]+1);
	
	if (f[x][1]==f[x-1][0]-v) L[x][1]=min(L[x][1],L[x-1][0]+1),R[x][1]=max(R[x][1],R[x-1][0]+1);
	if (f[x][1]==f[x-1][1]+a[x-1]) L[x][1]=min(L[x][1],L[x-1][1]),R[x][1]=max(R[x][1],R[x-1][1]);
}
void Dp(ll x)
{
	ll i;
	f[0][0]=0; L[0][0]=0; R[0][0]=0;
	f[0][1]=-inf; L[0][1]=-1; R[0][1]=-1;
	for (i=1;i<=n;i++) update(i,x);
}
ll find0(ll x,ll NK,ll v)
{
	//printf("%lld %lld %lld %lld\n",x,f[x][0],f[x-1][0]-v,v);
	if (f[x][0]==f[x-1][0]&&NK>=L[x-1][0]&&NK<=R[x-1][0]) return 1;
	else if (f[x][0]==f[x-1][0]-v&&NK>=L[x-1][0]+1&&NK<=R[x-1][0]+1) return 2;
	else if (f[x][0]==f[x-1][1]+a[x-1]&&NK>=L[x-1][1]&&NK<=R[x-1][1]) return 3;
	return -1;
}
ll find1(ll x,ll NK,ll v)
{
	if (f[x][1]==f[x-1][0]-v&&NK>=L[x-1][0]+1&&NK<=R[x-1][0]+1) return 1;
	else if (f[x][1]==f[x-1][1]+a[x-1]&&NK>=L[x-1][0]&&NK<=R[x-1][0]) return 2;
	return -1;
}
void get(ll K,ll v)
{
	ll i;
	ll NK=K,op=0;
	for (i=n;i>=1;i--)
	{
		ll A=-1;
		if (op==0) 
		{
			A=find0(i,NK,v);
			//printf("%lld %lld %lld\n",i,op,A);
			if (A==1) op=0;
			else if (A==2) ansl[++tot]=i,ansr[tot]=i,NK--,op=0;
			else op=1,ansr[++tot]=i;
		}
		else 
		{
			A=find1(i,NK,v);
			//printf("%lld %lld %lld\n",i,op,A);
			if (A==1) ansl[tot]=i,NK--,op=0;
			else op=1;
		}
	}
}
int main()
{
	freopen("d.in","r",stdin);
	freopen("d.out","w",stdout);
	read(n); read(K);
	//printf("%lld %lld\n",n,K);
	for (i=1;i<=n-1;i++) read(a[i]),s[i+1]=s[i]+a[i];
	//for (i=1;i<=n-1;i++) printf("%lld ",a[i]); printf("\n");
	ll l=-1e12,r=1e12;
	while (l<=r)
	{
		ll mi=(l+r)>>1;
		Dp(mi); 
		//printf("%lld\n",mi);
		if (K>=L[n][0]&&K<=R[n][0])
		{
			//printf("%lld %lld %lld \n",K,L[n][0],R[n][0]);
			ans=f[n][0]+K*mi;
			get(K,mi);
			break;
		}
		else if (K>R[n][0]) r=mi-1;
		else l=mi+1;
	}
	printf("%lld\n",ans);
	for (i=1;i<=tot;i++) printf("%lld %lld\n",ansl[i],ansr[i]);
	//printf("%lld %lld\n",s[89253]-s[2430],s[100000]-s[1]);
} 
posted @ 2021-07-23 14:07  OdtreePrince  阅读(438)  评论(0编辑  收藏  举报