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

砝码称重

一开始想到可以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. 封装 jsonp请求数据的方法

    什么是jsonp :  Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据. 为什么我们从不 ...

  2. 深入理解three.js中光源

    前言: Three.js 是一个封装了 WebGL 接口的非常好的库,简化了 WebGL 很多细节,降低了学习成本,是当前前端开发者完成3D绘图的得力工具,那么今天我就给大家详细讲解下 Three.j ...

  3. SpringBoot修改默认端口号 及 上下文

  4. [Linux] CentOS 显示 -bash: vim: command not found

    转载自:https://www.cnblogs.com/wenqiangwu/p/3288349.html i. 那么如何安裝 vim 呢?输入rpm -qa|grep vim 命令, 如果 vim ...

  5. Python3.7.4入门-2流程控制工具

    2 流程控制工具 记得在语句后加冒号 2.1 while # Fibonacci series: # the sum of two elements defines the next a, b = 0 ...

  6. 【linux】【jenkins】自动化部署一 安装jenkins及Jenkins工作目录迁移

    系统环境:Centos7 https://jenkins.io/zh/download/ 下载对应系统的jenkins 一.安装jdk8.0 jenkins安装需要jdk8or11,根据jenkins ...

  7. java数据结构——队列、循环队列(Queue)

    每天进步一点点,坚持就是成功. 1.队列 /** * 人无完人,如有bug,还请斧正 * 继续学习Java数据结构————队列(列队) * 队列和栈一样,都是使用数组,但是队列多了一个队头,队头访问数 ...

  8. 生产环境轻量级dns服务器dnsmasq搭建文档

    dnsmasq搭建文档 一.生产环境域名解析问题 之前生产环境设备较少,是通过维护master(192.168.1.1)设备的hosts文件实现的.每次新增设备后,需要在master的hosts文件中 ...

  9. 使用JSP+Servlet+Jdbc+Echatrs实现对豆瓣电影Top250的展示

    使用JSP+Servlet+Jdbc+Echatrs实现对豆瓣电影Top250的展示 写在前面: 有的小伙伴,会吐槽啦,你这个标题有点长的啊.哈哈 ,好像是的!不过,这个也是本次案例中使用到的关键技术 ...

  10. Spring 梳理 - WebMvcConfigurerAdapter详解

    参考:https://blog.csdn.net/weixin_43453386/article/details/83623242