2022/8/13 测试题解 (内含小木棍,奇怪的厨师,菜肴制作,喷泉,神奇的密码)
LINK:木棒
标签:dfs,剪枝

注(dfs剪枝的几个方面):
1、搜索顺序(优先搜索决策少的)
2、排除冗余信息(去掉重复的,没有用的)
3、可行性剪枝(如果当前方案到当前位置可以判断出已经不能继续执行,则return)
4、最优性剪枝(如果当前不是最优,则return)
4、记忆化剪枝(类似dp)
这道题明显是一道爆搜,但如果不剪枝明显超时 (以身试法)。可以得到以下剪枝:
①:从大到小排序,这样可以先排除不满足的条件
②:如果当前a[i] 拼接失败,那么所有与a[i] 相等的木棒都不需要再试了
③:当发现第一个用来拼接的木棍dfs后不能return true后,直接判定用当前minlen无法实现,直接return false(因为每根木棍都要用到,当前当前这根木棍不行,之后不管什么时候都不能用这根木棍);
  ④:同理,最后一根用来拼接的木棍dfs后不能return true后,直接判定当前方案false;
代码借用@Mr_Kingk
#include <cstring>
#include <iostream>
#include <algorithm>
const int maxn=70;
using namespace std;
bool vis[maxn];//vis[i]表示编号为i的木棍是否被用过(是否能用),true表示用过了(不能用了),false(没用过,能用)
int n,sum,minlen,sticks[maxn];
 
bool dfs(int cnt,int len,int pos)//cnt表示当前已经拼到第cnt根木棒,len表示当前这跟木棒的已经拼接的长度,pos表示枚举木棍的起点位置
{
    if(cnt*minlen==sum) return true;//当前拼成的木棒根数*当前每根木棒的长度=sum时,当前方案实现了,所以return true
    if(len==minlen) return dfs(cnt+1,0,0);//当前正在拼的这根木棒的长度=当前每根木棒的长度时,表示这根拼完了,继续从0开始拼下一根,cnt+1
    for(int i=pos;i<n;i++){//按编号递增枚举
        if(vis[i]) continue;
        int l=sticks[i];
        if(len+l<=minlen){
            vis[i]=true;
            if(dfs(cnt,len+l,i+1)) return true;
            vis[i]=false;
 
            if(!len) return false;//当发现第一个用来拼接的木棍dfs后不能return true后,直接判定用当前minlen无法实现,直接return false
            if(len+l==minlen) return false;//同理,最后一根用来拼接的木棍dfs后不能return true后,直接判定当前方案false;
            int j=i;
            while(j<n&&sticks[j]==l) j++;//如果中间过程中出现某根木棍不能用,则在拼接当前这根木棒的进程中将所有与之等长的木棍都pass,直接用下一个长度木棍继续拼接;
            i=j-1;
        }
    }
    return false;
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n&&n){
        sum=0,minlen=0;//sum表示n根木棍长度之和,minlen表示n根木棍的最大长度,即:一开始的最小可能长度
        memset(vis,false,sizeof vis);
        for(int i=0;i<n;i++){
            cin>>sticks[i];
   
            sum+=sticks[i];
            minlen=max(minlen,sticks[i]);
        }
        sort(sticks,sticks+n);
        reverse(sticks,sticks+n);//将sticks按从大到小排
        while(true){
            if(sum%minlen==0&&dfs(0,0,0)){
                cout<<minlen<<endl;
                break;
            }
            minlen++;
        }
    }
    return 0;
}
LINK:喷泉
标签:树状数组

乍一看像01背包啊,但是看看数据范围就呵呵了。
暴力代码如下:
#include<bits/stdc++.h>
using namespace std;
#define re register
inline int read() {
	re int sum=0,f=1;
	re char ch;
	while(ch<'0'||ch>'9') {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		sum=(sum<<1)+(sum<<3)+(ch^48);
		ch=getchar();
	}
	return sum*f;
}
struct C {
	int b,p;
}a[100005];
struct D {
	int b,p;
}w[100005];
bool cmp(C x,C y) {
	return x.b>y.b;
}
bool cmp1(D x,D y) {
	return x.b>y.b;
}
int main() {
	//freopen("fountain.in","r",stdin);
	//freopen("fountain.out","w",stdout);
	int n,c,d,idx=0,idx1=0;
	n=read(),c=read(),d=read();
	for(int i=1;i<=n;i++) {
		int a1,b1;
		char ch;
		a1=read(),b1=read();
		scanf(" %c",&ch);
		if(ch=='C') {
			a[++idx].b=a1,a[idx].p=b1;
		}
		else {
			w[++idx1].b=a1,w[idx1].p=b1;
		}
	}
	sort(a+1,a+idx+1,cmp),sort(w+1,w+idx1+1,cmp1);
	int qw=0,g=0,qw_1=0;
	for(int i=1;i<=idx;i++) {
		if(a[i].p<=c&&!g) {
			g=1;
			qw=i;
		}
		else if(a[i].p<=c&&g) {
			qw_1=i;
			break;
		}
	}
	int qw1=0,qw_1_=0;
	g=0;
	for(int i=1;i<=idx1;i++) {
		if(w[i].p<=d&&!g) {
			g=1;
			qw1=i;
		}
		else if(a[i].p<=c&&g) {
			qw_1_=i;
			break;
		}
	}
	if((!qw||!qw1)&&(!qw_1||!qw)&&(!qw1||!qw_1_)) {
		puts("0");
		return 0;
	}
	cout<<max(max(a[qw].b+a[qw_1].b,a[qw].b+w[qw1].b),w[qw1].b+w[qw_1_].b);
	return 0;
}
仔细分析一下选法,三种:
第一种,买两件C物品;
第二种,买两件D物品;
第三种,买一件C一件D。
我们可以很容易预处理出在C和D限制下能买的物品,显然第三种情况就是C的最大值加上D的最大值。
那么对于前两种方案,如果暴力查询那么复杂度肯定是不能接受的O(n2),考虑怎么优化。
将每件物品按照价格升序排序,同价格时按照魅力值降序排序。枚举每件物品,然后第二件物品就是剩下的钱可以买的魅力值最大的物品
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define maxn 100005
struct C{int p,b;}c[maxn];
struct D{int p,b;}d[maxn];
int ans,n,C,D,p,b,totc,totd;
char s[10];
int bit[maxn];
void update(int x,int val){
    while(x<=maxn-2){
        bit[x]=max(val,bit[x]);
        x+=x&-x;
    }
}
int query(int x){
    int res=0;
    while(x){
        res=max(res,bit[x]);
        x-=x&-x;
    }
    return res;
}
int main(){
    totc=totd=ans=0;
    scanf("%d%d%d",&n,&C,&D);
    for(int i=1;i<=n;i++){
        scanf("%d%d%s",&b,&p,s);
        if(s[0]=='C' && p<=C)
			c[++totc].p=p, c[totc].b=b;
        else if(s[0]=='D' && p<=D)
			d[++totd].p=p, d[totd].b=b; 
    }
    int max1=-1,max2=-1;
    for(int i=1;i<=totc;i++) 
		max1=max(max1,c[i].b);
    for(int i=1;i<=totd;i++) 
		max2=max(max2,d[i].b);
    if(max1+1 && max2+1) ans=max(ans,max1+max2); 
    memset(bit,0,sizeof bit); 
    for(int i=1;i<=totc;i++){
        int tmp=query(C-c[i].p);
        if(tmp>0)
            ans=max(ans,c[i].b+tmp);
        update(c[i].p,c[i].b);
    }
    
    memset(bit,0,sizeof bit);
    for(int i=1;i<=totd;i++){
        int tmp=query(D-d[i].p);
        if(tmp>0)
            ans=max(ans,d[i].b+tmp);
        update(d[i].p,d[i].b);
    }
    printf("%d\n",ans);
}
LINK:菜肴制作
标签:拓扑排序

这道题是一道经典的拓扑排序问题。有一个需要注意的点:题目要求的是序号小的点尽可能靠前而不是字典序最小。
就比如我们有两条边 4->1 , 2->3 。如果要字典序最小为:2->3->4->1,而题意为:4->1->2->3
  因此,为了使得我们的答案符合题目要求,我们可以进行以下这样的操作:在进行建边操作时,将所有的边进行反向建边,在搜索的时候使用大根堆存储我们的路径,并将先搜索到的点放到答案的最后面。
  这样,我们就可以尽可能的保证在符合题目构造的情况下,使得编号小的点在反向图里尽可能的靠后。那么,也就相当于在正向的图里编号小的点在尽可能靠前的位置。这样,就可以得到满足题意的答案。最后倒序输出即可。
#include<bits/stdc++.h>
#define ls(k) (k<<1)
#define rs(k) (k<<1|1)
using namespace std;
const int MAXN = 1e5+5;
int n,m,ans[MAXN],in[MAXN];
vector <int> e[MAXN];
bool topo()
{
    priority_queue <int> q;
    for(int i=1;i<=n;++i)
    if(!in[i]) q.push(i);
    
    int cnt=0;
    while(!q.empty())
    {
        int p=q.top();
        q.pop();
        ans[++cnt]=p;
        for(int i=0;i<e[p].size();++i)
        {
            int to=e[p][i];
            in[to]--;
            if(!in[to]) q.push(to);
    }
}
    for(int i=1;i<=n;++i)
    if(in[i]) return true;
    return false;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;++i) e[i].clear();
        memset(in,0,sizeof in);
        for(int i=1;i<=m;++i)
        {
            int u,v;
            scanf("%d %d",&u,&v);
            e[v].push_back(u);
            in[u]++;
        }
        if(topo()) printf("Impossible!\n");
        else
        {
            for(int i=n;i>=1;--i) printf("%d ",ans[i]);
            printf("\n");
        }
    }
    return 0;
}
LINK:奇怪的厨师

标签: RMQ
由于我们选的菜的数量不固定且要求菜的美味度的和的最大值。而每道菜的美味度是食材的美味度之和,且食材还必须连续。因此,我们可以先用前缀和求到前 种食材的价值和,然后用差分的方式来求解。那么,我们要如何得到美味度最大的 道菜呢?很容易想到的一个方法,就是直接暴力枚举,将所有的答案放入一个大根堆当中,最后从这个大根堆中取 个值出来。
暴力代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
ll a[500005],sum[500005];
int main() {
	//freopen("cook.in","r",stdin);
	//freopen("cook.out","w",stdout);
	re ll n,m,l,r;
    scanf("%d %d %d %d",&n,&m,&l,&r);
	sum[0]=0;
	priority_queue<ll,vector<ll>,greater<ll> > Q;
	for(re ll i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	for(re ll i=1;i<=n;i++) {
		for(re ll j=i+l-1;j<=i+r-1;j++) {
			if(j>n) break;
			if(Q.size()<m) Q.push(sum[j]-sum[i-1]);
			else {
				Q.push(sum[j]-sum[i-1]);
				Q.pop();
			}
		}
	}
	re ll ans=0;
	while(!Q.empty()) {
		ans+=Q.top();
		Q.pop();
	}
	printf("%lld",ans);
	return 0;
}
但是,这样的时间复杂度是极高的。由于我们只需要求到最大的M个值,并不需要得到所有的答案,我们只需要求到每个阶段的最值就可以了。因此,这里,我们可以使用RMQ来解决这个问题。
附RMQ模板
 void RMQ(){
	for(int j=1;j<=maxlog;j++){
		for(int i=1;i<=n;i++){
			if(i+(1<<j)-1<=n)
				dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
	}
}
  我们记前缀和数组为sum ,当一些菜的左端点点相同时,这些菜的美味度都是sum[r] - sum[l - 1], 要使得我们的答案最大,就需要找到最大的sum[r] 
  由此,对于一个右边界确定的菜,可以先跑一个[L + l - 1 , L + r - 1]的RMQ,找到其中的最大值,并提取出这个最小值所在的位置。
找到之后,又由于我们在以L为左端点的菜中还存在其他更小但是也是前M大的答案。
  因此,在取了这个最小值之后,假设这个值的位置是x,那么,我们就需要再计算 [L + l -1 , x - 1] 和[x + 1, L + r - 1].
正解步骤
- 
我们需要先算美妙度的前缀和,并初始化RMQ。 
- 
循环 i从 1到 n,因为以i为起点的 和弦 终点必定是 i + l - 1到i + r - 1之间,所以只要在区间内用RMQ取 超级和弦 ,并加入以美妙度从小排到大的优先队列中。
- 
取出堆顶元素,将美妙度加入 ans ,并将元素切为从 (当前元素的左边界 到 当前元素终点 - 1) 和 (当前元素终点 + 1 到 当前元素右边界) 两个部分,并再次加入优先队列,依次进行 k 次。 
- 
输出答案即可 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[500100],st[500100][21];
ll qr(int l,int r){
	ll mx=-1;
	int j=log2(r-l+1);
	if(a[st[l][j]]>a[st[r-(1<<j)+1][j]])return st[l][j];
	else return st[r-(1<<j)+1][j];
}
int n,k,l,r;
struct p{
	ll ori,l,r,val,mid;
bool operator<(const p &a)const{
	return a.val>val;
}
};
priority_queue<p> q;
int main()
{
	cin>>n>>k>>l>>r;
	ll tt;
	for(int i=1;i<=n;i++){
		cin>>tt;a[i]=a[i-1]+tt;
		st[i][0]=i;
	}
	for(int i=1;(1<<i)<=n;i++){
		for(int j=1;j+(1<<i)-1<=n;j++){
		if(a[st[j][i-1]]>a[st[j+(1<<(i-1))][i-1]])
		st[j][i]=st[j][i-1];
		else st[j][i]=st[j+(1<<(i-1))][i-1];
		}
	}ll ans=0;
	for(int i=0;i<=n;i++){
		int z=i+l,zz=min(n,i+r);
				if(z>n)break;
	p t;
	t.ori=i;t.val=a[qr(z,zz)]-a[i];t.l=z,t.r=zz;t.mid=qr(z,zz);
	q.push(t);
	}
	while(k--){
		p d=q.top();q.pop();
		ans+=d.val;
		p j,k;
		if(d.mid-1>=d.l&&d.mid-1<=d.r){
			j.l=d.l;j.r=d.mid-1;j.ori=d.ori;j.mid=qr(j.l,j.r);j.val=a[j.mid]-a[j.ori];
		q.push(j);
		}
		if(d.mid+1>=d.l&&d.mid+1<=d.r){
			k.l=d.mid+1;k.r=d.r;k.ori=d.ori;k.mid=qr(k.l,k.r);k.val=a[k.mid]-a[k.ori];
		q.push(k);
		}
	}
	cout<<ans;
}
LINK:神奇的密码
标签:并查集

T5却是全场最水,看到如果字符串a与字符串c等价,字符串b也与字符串c等价 就明白用并查集。。。再看包含了同一个字母 就明白这个并查集实际上只与字母有关。对于每一个字符串,都将s[0] 与 s[1~len] 归并,最后统计每一个出现的字母即可。
#include<bits/stdc++.h>
using namespace std;
int fa[30];
int Find(int x) {
	if(x==fa[x]) return x;
	return fa[x]=Find(fa[x]);
}
set<int> st;
int main() {
	//freopen("password.in","r",stdin);
	//freopen("password.out","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=26;i++) fa[i]=i;
	while(n--) {
		string s;
		cin>>s;
		st.insert(s[0]-'a'+1);
		for(int i=1;i<s.length();i++) {
			st.insert(s[i]-'a'+1);
			if(Find(s[i]-'a'+1)==Find(s[0]-'a'+1)) continue;
			fa[Find(s[i]-'a'+1)]=Find(s[0]-'a'+1);
		}
	} 
	int ans=0;
	for(set<int>::iterator it=st.begin();it!=st.end();it++) {
		if(*it==Find(*it)) {
			ans++;
		}
	}
	printf("%d",ans);
	return 0;
}
总结与反思
今天的测试有两道原题,菜肴制作 和 奇怪的厨师 ,但是都没有做出来。我认为这是我平时的不求甚解,只知道思路但代码实现能力太差,平时的自主复习能力不强。今后的学习中,不管每一道题有多难,不能只理解,还要自己熟练地打出来,不要嫌浪费时间,也不要急着刷新的题,把每一道做的题理解清楚。不是为了考试,而是为了突破自己。
                              
本文来自博客园,作者:Doria_tt,转载请注明原文链接:https://www.cnblogs.com/pangtuan666/p/16583197.html
 

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号