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

砝码称重

一开始想到可以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;
}

NOIP_TG的更多相关文章

随机推荐

  1. Java 多线程实现接口Runnable和继承Thread区别(转)

    Java 多线程实现接口Runnable和继承Thread区别 Java中有两种实现多线程的方式.一是直接继承Thread类,二是实现Runnable接口.那么这两种实现多线程的方式在应用上有什么区别 ...

  2. IDEA中输出syso的快捷键设置

    1. 2. 3. 4. 5.上图中的第三步会出现警告,那个红色的字,点击Define,选择Java 6.之后点击Apply和OK即可

  3. IDEA中把一个Maven工程安装到本地仓库

    首先说明问题,我需要使用PageHelper插件,但是这个分页插件是改造过的,是一个pom工程,现在需要当作一个jar包使用,所以需要安装到本地仓库 1. 2. 3. 4. 5.成功 6.需要使用该j ...

  4. iOS代码混淆

    混淆原理 代码编译阶段将符号(方法名.属性名等)替换成随机生成的字符串 长话短说,直接上步骤. 混淆集成步骤 步骤一.创建shell文件(confuse.sh)并配置相应的运行环境. 在项目根目录下新 ...

  5. Vue.js学习总结——1

    1.什么是Vue.js 1.Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架 2.Vue.js 是前端的主流框架之一,和Angular.js.React.js 一起,并成为前端 ...

  6. FreeSql (十九)多表查询

    多表查询,常用的有联表 LeftJoin/InnerJoin/RightJoin ,这三个方法在上篇文章已经介绍过. 除了联表,还有子查询 Where Exists,和 Select 子表: IFre ...

  7. 字节输出流OutputStream

    1.OutputStream是输出字节流的超类. import java.io.File; import java.io.FileOutputStream; import java.io.IOExce ...

  8. Mysql高手系列 - 第14篇:详解事务

    这是Mysql系列第14篇. 环境:mysql5.7.25,cmd命令中进行演示. 开发过程中,会经常用到数据库事务,所以本章非常重要. 本篇内容 什么是事务,它有什么用? 事务的几个特性 事务常见操 ...

  9. Python学习-函数,函数参数,作用域

    一.函数介绍 函数定义:函数时组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 我们已经知道python提供了许多内建函数,print(), type()等.我们也可以自己创建函数,这被叫 ...

  10. opencv图像处理之常见滤波器

    图像平滑 Smoothing, also called blurring, is a simple and frequently used image processing operation. 平滑 ...