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号