NOIP_TG

本博客主要记录一些在刷题的途中遇到的一些巧妙的题目

砝码称重

一开始想到可以DP递推标记能凑成的数量

但发现同一种砝码可能有多个于是想多开一维状态存当前还剩多少砝码

真是愚蠢至极

直接把所有砝码单独看待不就行了么。。。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int a[10010],x,num,b[10]={0,1,2,3,5,10,20},ans;
bool t[10010];
int main()
{
    for(int i=1;i<=6;i++)
    {
        cin>>x;
        for(int j=1;j<=x;j++)  a[++num]=b[i];  //在a数组里记录所有砝码重 
    }
    for(int i=1;i<=num;i++){  //num即为a数组中元素的个数 
        for(int j=1005;j>=1;j--)  if(t[j])  t[j+a[i]]=1;  
        t[a[i]]=1;
    }
    for(int i=1;i<=1005;i++)  if(t[i])  ans++;  //如果某一位为true说明可能出现这种情况 
    printf("Total=%d",ans);
    return 0;
}

车站

由于不知道第二站上车人数

所以设为y

通过推式子可以得到m的表达式于是解出来y

再模拟一遍就OK啦

#include<bits/stdc++.h>
using namespace std;
int a[25]={0,1,1,2},b[25]={0,0,0,0};
int aa,bb,n,m,x; 
int main()
{
    scanf("%d%d%d%d",&aa,&n,&m,&x);
    for(int i=4;i<n;i++)
    {
        a[i]=a[i-2]+a[i-1]-1;
        b[i]=b[i-2]+b[i-1]+1;
    }
    bb=(m-aa*a[n-1])/b[n-1];
    printf("%d",aa*a[x]+bb*b[x]);
    return 0;
}

进制转换

感谢题解区大佬

被除数=商*除数+余数,这是解决问题的关键

例如在C++里,-15%-2=-1,-15/-2=7,而7*-2+(-1)=-15

但是因为我们是不断取余数倒序为转换结果,所以余数不能出现负数,那怎么办呢?

很简单虽然我一开始看不懂

我们只需要将商+1,余数-除数即可,因为余数(绝对值)一定小于除数,所以这样就可以将余数转换为正数

正确性证明:

(商+1)*除数+(余数-除数)=商*除数+除数+余数-除数=商*除数+余数=被除数
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
void zhuan(int n,int r){
    if(n==0) return ;
    int m=n%r;//m为余数 
    if(m<0) m-=r,n+=r;//如果余数小于0,转化为正数(-5%2=1,-5&-2=-1,所以m-=r后必为正数)
    //将余数转化为ascll码方便输出,省略了一个数组 
    if(m>=10) m='A'+m-10;
    else m+='0';
    zhuan(n/r,r);
    printf("%c",m);//注意,因为结果为余数倒序,输出要写在递归后面,不然会顺序输出 
    return ;
}
int main(){
    int n,r;
    string ans="";
    cin>>n>>r;
    cout<<n<<"=";
    zhuan(n,r);
    printf("(base%d)",r);
    return 0;
}

合唱队形

感谢

首先,我们要想出列最少,那么就想要留下的最多。很容易想的最长升,但是,这个序列是一个中间高,两头底的序列,最长升只能处理出单调性的序列。

那么怎么做到呢?

我们先看从T1到Ti这一段单调递增的序列,再看Ti到TK这一段单调递减的序列,那么问题就解决了。先从1到n求一趟最长升,然后从n到1也求一趟,最后枚举中间的Ti,然后从众多Ti中挑个大的。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[105],f[2][105],ans;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    a[0]=0;
    for(int i=1;i<=n;i++)//从1到n求最长升 
    for(int j=0;j<i;j++) if(a[i]>a[j]) f[0][i]=max(f[0][i],f[0][j]+1);
    a[n+1]=0;
    for(int i=n;i;i--)//从n到1求最长升 
    for(int j=n+1;j>i;j--) if(a[i]>a[j]) f[1][i]=max(f[1][i],f[1][j]+1);
    for(int i=1;i<=n;i++) ans=max(f[0][i]+f[1][i]-1,ans);//枚举Ti,从1到Ti的最长升+从TK到Ti的最长升-1(Ti被加了两次) 
    printf("%d\n",n-ans);
    return 0;
}

花匠

可以DP

if(a[i]>a[i-1])f[i][0]=f[i-1][1]+1;
else f[i][0]=f[i-1][0];
if(a[i]<a[i-1])f[i][1]=f[i-1][0]+1;
else f[i][1]=f[i-1][1];

也可以贪心

就考虑第一个(一定会选)后面接上升的或者下降的。。。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<queue>
#define RG register
#define N 100100
#define ll long long
#define ld long double
using namespace std;

inline ll read(){
  RG ll x=0,o=1; RG char ch=getchar();
  while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
  if(ch=='-') o=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
  return x*o;
}

int main(){
    int n=read(),las=read(),op=0,ans=0;
    //先处理一个点防炸裂,不过最好特判下n=1
    //op记录上一个单调序列的种类 1是上升 2是下降
    //las为上一个高度
    for(RG int i=2;i<=n;++i){
        int x=read();
        if(x==las) continue ;
        //若不满足上一个序列单调性
        if(op!=1&&x>las) ++ans,op=1;
        else if(op!=2&&x<las) ++ans,op=2;
        las=x; 
    } cout<<ans+1;
}

信息传递

建边之后找最小环

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 200010;
int n, fa[N], ans = 0x3f3f3f3f;
int get (int x, int &cnt) { //cnt记录环的长度 
    cnt ++;
    if (fa[x] == x) return x;
    else return get(fa[x], cnt);
}
int main () {
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
        fa[i] = i;
    for (int i = 1; i <= n; i ++) {
        int cnt = 0, f;
        scanf("%d", &f);
        if (get(f, cnt) == i) {
            ans = min(ans, cnt); //维护最小的环 
        }else
            fa[i] = f;
    }
    printf("%d", ans);
    return 0;
}

方格取数

两条线路同时DP

直接设四维存储每个人的坐标再更新就好啦

但当数据范围比较大的时候可以用路径长度去掉一维

具体解释

#include<bits/stdc++.h>
using namespace std;
int n,a[20][20],dp[20][20][20][20];
int main(){
	cin>>n;
	int cn,cm,ck;
	while(cin>>cn>>cm>>ck){
        if(!cn)break;
		a[cn][cm]=ck;
	}
    for(int i = 1; i <= n ; i ++) 
        for(int j = 1; j <= n; j ++) 
            for(int x = 1; x <= n; x ++) 
                for(int y = 1; y <= n; y ++) { 
                    if(i != x || j != y) dp[i][j][x][y] = max(max(dp[i - 1][j][x - 1][y],dp[i][j - 1][x][y - 1]),max(dp[i][j - 1][x - 1][y],dp[i - 1][j][x][y - 1])) + a[i][j] + a[x][y]; 
                    else dp[i][j][x][y] = max(max(dp[i - 1][j][x - 1][y],dp[i][j - 1][x][y - 1]),max(dp[i][j - 1][x - 1][y],dp[i - 1][j][x][y - 1])) + a[i][j]; 
                }
    cout<<dp[n][n][n][n];
}

旅行家的预算

我们思考一下贪心策略

从本站往后搜索加油站

如果有比本站油价更低的就加上到那站的油

如 A可以到B、C站,p[B]>p[A]>p[C],如果我们选择到B站去加油再到C站那在B站为了到C站加的油还不如在A站加上

如果没有那就在本站加满因为既然后面的油价都比本站贵那还不如在本站加满再走

如 A可以到B站,不能到C站,但是能到B和C之间的某个位置,且p[B]>p[A]>p[C],如果同理我们可以在A站把油加满再到B站加上到C站的油,再去C站加油。这样比在A站加到B站的油再加到C站的油更优

#include<bits/stdc++.h>
using namespace std;
int n;
double ci,d1,v,d2,p[10],d[10],ma,ans,le;//总距离,油箱容量,每升汽油能行驶的距离,出发时油价,沿途加油站数目
int main(){
	cin>>d1>>v>>d2>>p[0]>>n;
	for(int i=1;i<=n;i++)cin>>d[i]>>p[i];
	ma=v*d2*1.0;
	for(int i=0;i<=n;){
		double mi=0x7fffffff;int minn;
		for(int j=i+1;d[j]<=d[i]+ma&&j<=n;j++)
			if(mi>p[j])
				mi=p[j],minn=j;
		if(mi==0x7fffffff&&i!=n){cout<<"No Solution";return 0;}
		if(d1-d[i]<=ma){
			if(mi>p[i]||i==n){
				ans+=((d1-d[i])*1.0/d2-le)*p[i];printf("%.2lf",ans);
				return 0;
			}
		}
		if(mi<p[i])ans+=((d[minn]-d[i])*1.0/d2-le*1.0)*1.0*p[i],le=0;
		else ans+=(v-le)*p[i],le+=v-(d[minn]-d[i])*1.0/d2;
		i=minn;
	}
	cout<<ans;
}

乘积最大

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100];
struct node{
	int s[100],len;
}f[100][100],kong;
node cal(node c,int l,int r){
	node re=kong,d=kong;
	for(int i=r;i>=l;i--){
		re.s[r-i+1]=a[i];
	}
	int lena=re.len=r-l+1,lenb=c.len;
	for(int i=1;i<=lenb;i++){
		int jin=0;
		for(int j=1;j<=lena;j++){
			d.s[i+j-1]+=re.s[j]*c.s[i]+jin;
			jin=d.s[i+j-1]/10;
			d.s[i+j-1]%=10;
		}
		d.s[lena+i]=jin;//i<=lenb->lena+i
	}
	int leng=lena+lenb;
    while(leng>1&&d.s[leng]==0)leng--;
    d.len=leng;
    return d;
}
node ma(node aa,node bb){
	if(aa.len>bb.len)return aa;
	if(aa.len<bb.len)return bb;
	for(int i=aa.len;i>=1;i--){
		if(aa.s[i]<bb.s[i])return bb;
		if(aa.s[i]>bb.s[i])return aa;
	}
	return aa;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		char ch;cin>>ch;a[i]=ch-'0';
		for(int j=i;j>=1;j--){
			f[i][0].s[++f[i][0].len]=a[j];
		}
		
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<=m&&j<=i-1;j++){
			for(int k=j-1;k<i;k++){
				f[i][j]=ma(f[i][j],cal(f[k][j-1],k+1,i));
			}
		}
	}
	for(int i=f[n][m].len;i>=1;--i)
      printf("%d",f[n][m].s[i]);
}
/*
f[i][j]当前第i个数,插入了j个乘号的最大值
f[i][j]=max(f[i][j],f[k][j-1]*(a[k+1]~a[i]))
*/

邮票面值设计

先dfs再dp求最大值

这种做法实属少见

dp时01背包打错啦 差点身败名裂

今天考试也是背包内循环打反调了好久。。。

#include<bits/stdc++.h>
using namespace std;
int n,K,ans[1000],mem[1000],len,maxn,f[100000];
int dp(int k,int ma){
	memset(f,127,sizeof(f));
	f[0]=0;
	for(int i=1;i<=k;i++){
		for(int j=mem[i];j<=mem[k]*n;j++){//最大能表示的数就是mem[k]*n
			f[j]=min(f[j],f[j-mem[i]]+1);
		}
	}
	int re=1;
	while(f[re+1]>0&&f[re+1]<=n)++re;
	return re;
}
void dfs(int k,int ma){
	if(k>K){
		if(ma>maxn){
			for(int i=1;i<=K;i++)ans[i]=mem[i];
			maxn=ma;
		}
		return ;
	}
	for(int i=mem[k-1]+1;i<=ma+1;i++){//最大能加入的数就是ma+1
		mem[k]=i;
		dfs(k+1,dp(k,ma));
	}
}
int main(){
	cin>>n>>K;
	dfs(1,0);
	for(int i=1;i<=K;i++){
		printf("%d ",ans[i]);
	}cout<<endl;
	cout<<"MAX="<<maxn;
	return 0;
}

关路灯

本以为我的区间DP已经搞会了

直达见到这道题...

感觉自己学区间DP学傻了

只知道一味套模板先枚举区间长度再是左右端点

但其实还可以从一个区间开始往左右延伸

正事:

状态:

f[i][j][0]表示关掉[i,j]的灯之后,他在i点

f[i][j][1]表示关掉[i,j]的灯之后,他在j点

转移:

f[i][j][0]=min(f[i+1][j][1]+(a[j]-a[i])(sum[i]+sum[n]-sum[j]),f[i+1][j][0]+(a[i+1]-a[i])(sum[i]+sum[n]-sum[j]));

f[i][j][1]=min(f[i][j-1][1]+(a[j]-a[j-1])(sum[i-1]+sum[n]-sum[j-1]),f[i][j-1][0]+(a[j]-a[i])(sum[i-1]+sum[n]-sum[j-1]));

#include<bits/stdc++.h>
using namespace std;
int a[60],b[60],sum[60],n,m,c;
int f[60][60][2];
int main(){
  scanf("%d%d",&n,&c);
  memset(f,127,sizeof(f));
  for(int i=1;i<=n;i++)
    scanf("%d%d",&a[i],&b[i]),sum[i]=sum[i-1]+b[i];
  f[c][c][0]=f[c][c][1]=0;
  for(int l=2;l<=n;l++)
    for(int i=1;i+l-1<=n;i++){
    int j=i+l-1;
    f[i][j][0]=min(f[i+1][j][1]+(a[j]-a[i])*(sum[i]+sum[n]-sum[j]),f[i+1][j][0]+(a[i+1]-a[i])*(sum[i]+sum[n]-sum[j]));
    f[i][j][1]=min(f[i][j-1][1]+(a[j]-a[j-1])*(sum[i-1]+sum[n]-sum[j-1]),f[i][j-1][0]+(a[j]-a[i])*(sum[i-1]+sum[n]-sum[j-1]));
        }
  int ans=min(f[1][n][0],f[1][n][1]);
  printf("%d",ans);
  return 0;
}

双栈排序

二分图好题...

首先从一个栈的开始想

就能放就放嘛,维护栈中的最大值如果要加入的第一个元素比栈中的最大值小就弹栈直到比第一个元素大为止

虽然的想法这样是错

但对于30分的数据足够了

#include<bits/stdc++.h>
using namespace std;
int n,a[100005],b[100005],maxn[100005],top,ji;
char ans[1000005];
stack<int>dui;
int main(){
	cin>>n;
	if(!n){cout<<"0"<<endl;return 0;}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dui.push(a[1]);
	ans[++ji]='a';
	maxn[++top]=a[1];
	for(int i=2;i<=n;i++){
		if(maxn[top]>a[i]){
			ans[++ji]='a';
			dui.push(a[i]);
			++top;
			maxn[top]=max(maxn[top-1],a[i]);
		}else {
			while(maxn[top]<a[i]&&top){
				ans[++ji]='b';
				b[++b[0]]=dui.top();
				maxn[top--]=0;
				dui.pop();
			}
			dui.push(a[i]);
			ans[++ji]='a';
			++top;
			maxn[top]=max(maxn[top-1],a[i]);
		}
	}
	while(dui.size()){
		ans[++ji]='b';
		b[++b[0]]=dui.top();
		dui.pop();
	}
	for(int i=1;i<n;i++){
		if(b[i]!=b[i+1]-1){cout<<"0"<<endl;return 0;}
	}
	for(int i=1;i<=ji;i++){
		cout<<ans[i]<<" ";
	}
}

正解:

这是真正的正解,LG题解里面的许多都会被hack

不信你就试试这组数据

5
2 4 1 3 5

ans:a c a b b a b a d b 

我们发现在\(i<j<k\)

如果\(a_k<a_i<a_j\)则必定无法用两个栈排序

于是我们就找到了i和j要分别进入两个栈时必须满足的条件

由于这种关系会把所有数字分成两边

于是就想到了二分图(不是匹配啦)

那就很好办了

把两边的数字染成不同的颜色

一种颜色的数字加入同一个栈

最后还要注意一下操作顺序

如果现在可以弹第二个栈但是又可以把下一个数字加入第一个栈

那就先入栈再出栈因为要字典序最小

#include<bits/stdc++.h>
using namespace std;
int n,t[10005],s[10005],col[10005];
bool ed[1005][1005];
int mi(int aa,int bb){return aa>bb?bb:aa;}
void dfs(int k,int c){
	for(int i=1;i<=n;i++){
		if(!col[i]&&ed[k][i]){
			col[i]=3-c;
			dfs(i,3-c);
		}if(ed[k][i]&&col[i]==col[k]){cout<<"0";exit(0);}
	}
}
void build(){//建立二分图
	s[n+1]=0x7fffffff;
	for(int i=1;i<=n;i++)s[i]=t[i];
	for(int i=n;i>=1;i--)s[i]=mi(s[i],s[i+1]);
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(t[i]<t[j]&&t[i]>s[j]){//按照结论建图
				ed[i][j]=ed[j][i]=1;
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(!col[i]){
			col[i]=1;
			dfs(i,1);
		}
	}
	//for(int i=1;i<=n;i++)cout<<col[i]<<" ";cout<<endl;
}
stack<int>dui1,dui2;
void work(){
	int now=0;
	for(int i=1;i<=n;i++){
		if(col[i]==1){
			dui1.push(t[i]);
			cout<<"a ";
		}else {
			dui2.push(t[i]);
			cout<<"c ";
		}
		while(dui1.size()&&dui1.top()==now+1||(dui2.size()&&dui2.top()==now+1&&(col[i+1]==2||i==n))){
			if(dui1.size()&&dui1.top()==now+1){
				cout<<"b ";
				dui1.pop();
				++now;
			}else{
				cout<<"d ";
				dui2.pop();
				++now;
			}
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>t[i];
	build();
	work();
}

加分二叉树

区间DP,小区间得大区间

#include<cstdio>
using namespace std;
int n,v[39],f[47][47],i,j,k,root[49][49];
void print(int l,int r){
    if(l>r)return;
    if(l==r){printf("%d ",l);return;}
    printf("%d ",root[l][r]);
    print(l,root[l][r]-1);
    print(root[l][r]+1,r);
}
int main() {
    scanf("%d",&n);
    for( i=1; i<=n; i++) scanf("%d",&v[i]);
    for(i=1; i<=n; i++) {f[i][i]=v[i];f[i][i-1]=1;}
    for(i=n; i>=1; i--)
        for(j=i+1; j<=n; j++)
            for(k=i; k<=j; k++) {
                if(f[i][j]<(f[i][k-1]*f[k+1][j]+f[k][k])) {
                    f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
                    root[i][j]=k;
                }
            }
    printf("%d\n",f[1][n]);
    print(1,n);
    return 0;
}

摩天大楼里的奶牛

状压DP

但要另外开一个g数组存当前状态的剩余体积

using namespace std;
int n,m,a[100005],f[1000005],g[1000005];
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++)cin>>a[i];
	memset(f,127,sizeof(f));
	f[0]=0;
	for(int i=1;i<(1<<n);i++){
		for(int j=0;j<n;j++){
			if(i&(1<<j)){
				int s=i^(1<<j);
				if(g[s]>=a[j]&&f[i]>=f[s]){
					if(f[i]==f[s])g[i]=max(g[i],g[s]-a[j]);
					else g[i]=g[s]-a[j];
					f[i]=f[s];
				}
				if(g[s]<a[j]&&f[i]>f[s]){
					g[i]=m-a[j];
					f[i]=f[s]+1;
				}
			}
		}
	}
	cout<<f[(1<<n)-1]<<endl;
}
posted @ 2019-08-26 10:48  lqhsr  阅读(247)  评论(0编辑  收藏  举报