【黑历史】刷水翻车记录
前言
由于自己是全机房唯一一个高一才开始学OI的人,老师又懒得教(不是,全靠自学苟活至今,学的东西不完整,所以自己上洛谷刷刷水题补一些基础的东西
说白了就是想摸鱼
P1012 拼数
因为每个数字的长度不同不好比较,所以要通过把数字组合起来考虑,这样它们的长度就是一样的
自己想的解法是一个一个把数字加到不同位置进行比较,类似于贪心,题解给的方法类似冒泡排序加贪心,若两个数正着放的结果小于倒着放的结果则交换位置。
记下这个题的原因是现在才知道string类型居然是可以加减的
#include<string>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
string a[25];int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)
if(a[i]+a[j]<a[j]+a[i])swap(a[i],a[j]);
for(int i=1;i<=n;i++)cout<<a[i];
}
P1090 合并果子
记录一下优先队列的基本用法,具体可看Crloss的博客,好像这道题还可以用哈夫曼树的思想来理解
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素
priority_queue <int,vector<int>,greater<int> > q;//权值小的优先
//注意后面两个“>”不要写在一起,“>>”是右移运算符
priority_queue <int,vector<int>,less<int> >q;//权值大的优先
//实在不行写结构体,重载小于运算符,那么就是大的优先
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;int n;long long ans;
priority_queue<long long,vector<long long>,greater<long long> >q;
int main()
{
scanf("%d",&n);for(int i=1,a;i<=n;i++)scanf("%d",&a),q.push(a);
for(int i=1;i<n;i++)
{
long long a,b;
a=q.top();q.pop();
b=q.top();q.pop();
ans+=a+b;q.push(a+b);
}
printf("%lld\n",ans);
}
P1803 凌乱的yyy / 线段覆盖
记得第一次我在考场上写的是树状数组优化dp,后来发现贪心爆简单。
按右端点排序后从左到右选取,遇到第一个可选的就选。这样子可以保证当前线段对后面选择的限制最小。
#include<cstdio>
#include<algorithm>
using namespace std;
int n,ans,L[1000005],R[1000005],q[1000005];
bool cmp(int a,int b){return R[a]==R[b]?L[a]>L[b]:R[a]<R[b];}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&L[i],&R[i]),q[i]=i;
sort(q+1,q+1+n,cmp);
for(int i=1,r=-1;i<=n;i++)if(L[q[i]]>=r)r=R[q[i]],ans++;
printf("%d\n",ans);
}
P1080 国王游戏
被高精度乘法教做人。。。。。。
考虑两个大臣i与j,把他们位置放入两个位置p1与p2(p1<p2)
设1到(p1-1)的大臣左手中的数的乘积为mul1,(p1+1)到(p2-1)的大臣左手的数乘积为mul2
把i放到p1,j放到p2他们得到金币的最大值为$max(\frac {mul1}{b_i},\frac {a_imul1mul2}{b_j})$
把j放到p1,i放到p2他们得到金币的最大值为$max(\frac {mul1}{b_j},\frac {a_jmul1mul2}{b_i})$
根据题意应该取最大值中较小的那个。
显然有$\frac {mul1}{b_i} \leq \frac {a_jmul1mul2}{b_i}$与$\frac {mul1}{b_j} \leq \frac {a_imul1mul2}{b_j}$
所以直接比较$\frac {a_imul1mul2}{b_j}$与$\frac {a_jmul1mul2}{b_i}$的大小就好了
如果$\frac {a_imul1mul2}{b_j} \leq \frac {a_jmul1mul2}{b_i}$就有$a_ib_i \leq a_jb_j$
这个时候应该把i放到p1,j刚到p2才能使最大值最小,又因为(p1<p2),所以如果$a_ib_i \leq a_jb_j$,那么i应该放到j前面
直接按$a_ib_j$排序就好了,剩下的就交给高精度
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,A,B,a[1005],b[1005],q[1005];
struct node{int len,s[5005];}ans,sum;
bool cmp(int x,int y){return a[x]*b[x]<a[y]*b[y];}
node operator *(node a,int b)
{
for(int i=1;i<=a.len;i++)a.s[i]*=b;
for(int i=1;i<=a.len;i++)a.s[i+1]+=a.s[i]/10,a.s[i]%=10;
while(a.s[a.len+1])a.len++,a.s[a.len+1]+=a.s[a.len]/10,a.s[a.len]%=10;return a;
}
node operator /(node a,int b)
{
for(int i=a.len;i>=1;i--)a.s[i-1]+=a.s[i]%b*10,a.s[i]/=b;
while(a.len>1&&!a.s[a.len])a.len--;
return a;
}
bool operator <(node a,node b)
{
if(a.len!=b.len)return a.len<b.len;
for(int i=a.len;i>=1;i--)if(a.s[i]!=b.s[i])return a.s[i]<b.s[i];
return 0;
}
int main()
{
long long sum1=1;scanf("%d%d%d",&n,&A,&B);sum.len=1;sum.s[1]=1;sum=sum*A;sum1=A;
for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),q[i]=i;
sort(q+1,q+1+n,cmp);
for(int i=1;i<=n;i++)
{
if(ans<sum/b[q[i]])ans=sum/b[q[i]];
sum=sum*a[q[i]];sum1*=a[q[i]];
}
for(int i=ans.len;i;i--)printf("%d",ans.s[i]);return 0;
}
P1092 虫食算
永远学不会玄学剪枝暴力出奇迹的我。。。。。。
#include<cstdio>
#include<cstring>
#include <cstdlib>
int n,vis[30],num[30];char s[5][30];
int id(char a){return a-'A'+1;}
void dfs(int x,int y,int d)
{
if(x==0){if(d==0){for(int i=1;i<=n;i++)printf("%d%c",num[i],i==n?'\n':' ');exit(0);}return;}
for(int i=x-1;i>=1;i--)
{
int w1=num[id(s[1][i])],w2=num[id(s[2][i])],w3= num[id(s[3][i])];
if(w1==-1||w2==-1||w3==-1)continue;
if((w1+w2)%n!=w3&&(w1+w2+1)%n!=w3)return;
}
if(num[id(s[y][x])]==-1)
{
for(int i=n-1;i>=0;i--)if(!vis[i])
{
if(y!=3)
{
num[id(s[y][x])]=i;vis[i]=1;
dfs(x,y+1,d);
num[id(s[y][x])]=-1;vis[i]=0;
}
else
{
int w=num[id(s[1][x])]+num[id(s[2][x])]+d;
if(w%n!=i)continue;
num[id(s[y][x])]=i;vis[i]=1;
dfs(x-1,1,w/n);
num[id(s[y][x])]=-1;vis[i]=0;
}
}
}
else
{
if(y!=3)dfs(x,y+1,d);
else
{
int w=num[id(s[1][x])]+num[id(s[2][x])]+d;
if(w%n!=num[id(s[3][x])])return;
dfs(x-1,1,w/n);
}
}
}
int main()
{
scanf("%d%s%s%s",&n,s[1]+1,s[2]+1,s[3]+1);
memset(num,-1,sizeof num);dfs(n,1,0);
}
P1073 最优贸易
已经菜到连边都可以打错了
比较容易想到求连通分量后缩点,存下每个连通块的最小值最大值,然后爆搜即可
我就是不用tarjin,你来打我啊
记得要用记忆化搜索,不然会丢30分(不要问我怎么知道的
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100005;
const int maxm=1000005;
int ans,ok[maxn];
int q[maxn],vis[maxn],ori[maxn],l[maxn][2];
int n,m,id,cnt,val[maxn],mn[maxn],mx[maxn];
int info[maxn][3],nx[maxm][3],v[maxm][3],ecnt[3];
void add(int u1,int v1,int k){nx[++ecnt[k]][k]=info[u1][k];info[u1][k]=ecnt[k];v[ecnt[k]][k]=v1;}
void dfs1(int x){vis[x]=1;for(int i=info[x][0];i;i=nx[i][0])if(!vis[v[i][0]])dfs1(v[i][0]);q[n-(++id)+1]=x;}
void dfs2(int x,int f){ori[x]=f;mx[f]=max(mx[f],val[x]);mn[f]=min(mn[f],val[x]);for(int i=info[x][1];i;i=nx[i][1])if(!ori[v[i][1]])dfs2(v[i][1],f);}
void dfs(int x,int Mn)
{
if(ok[x])return;ok[x]=1;
Mn=min(mn[x],Mn);
if(ori[n]==x){ans=max(ans,mx[x]-Mn);vis[x]=1;return;}
for(int i=info[x][2];i;i=nx[i][2])
{
dfs(v[i][2],Mn);
if(vis[v[i][2]])vis[x]=1,mx[x]=max(mx[x],mx[v[i][2]]);
}
if(vis[x])ans=max(ans,mx[x]-Mn);
}
int main()
{
scanf("%d%d",&n,&m);
memset(mn,0x3f,sizeof mn);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
for(int i=1,u1,v1,_;i<=m;l[i][0]=u1,l[i][1]=v1,i++)
{scanf("%d%d%d",&u1,&v1,&_);add(u1,v1,0);add(v1,u1,1);if(_==2)add(v1,u1,0),add(u1,v1,1);}
for(int i=1;i<=n;i++)if(!vis[i])dfs1(i);for(int i=1;i<=n;i++)if(!ori[q[i]])cnt++,dfs2(q[i],cnt);
for(int i=1;i<=m;i++)if(ori[l[i][0]]!=ori[l[i][1]])add(ori[l[i][0]],ori[l[i][1]],2);
memset(vis,0,sizeof vis);dfs(ori[1],0x3f3f3f3f);
printf("%d\n",ans);
}
P1074 靶形数独
还是永远不会爆搜的我。。。。。。
这题的剪枝就是每次选空的最小的行进去填充。
#include<cstdio>
#include<algorithm>
using namespace std;
int ans=-1,q[15],nx[15],cnt[15],ma[15][15],mp[5][15][15];
bool cmp(int a,int b){return cnt[a]>cnt[b];}
int ori(int x,int y){return (x-1)/3*3+(y-1)/3+1;}
void dfs(int x,int y)
{
if(x<=9&&y>9){dfs(nx[x],1);return;}
if(x==0)
{
int sum=0;
for(int i=1;i<=4;i++)
{
int l=i,r=10-i;
for(int mid=l+1;mid<=r-1;mid++)sum+=(i+5)*(ma[i][mid]+ma[mid][i]+ma[10-i][10-mid]+ma[10-mid][10-i]);
sum+=(i+5)*(ma[i][i]+ma[i][10-i]+ma[10-i][i]+ma[10-i][10-i]);
}
sum+=10*ma[5][5];ans=max(ans,sum);return;
}
if(ma[x][y]){dfs(x,y+1);return;}
for(int i=9;i>=1;i--)if((!mp[1][x][i])&&(!mp[2][y][i])&&(!mp[3][ori(x,y)][i]))
mp[1][x][i]=mp[2][y][i]=mp[3][ori(x,y)][i]=1,ma[x][y]=i,dfs(x,y+1),
mp[1][x][i]=mp[2][y][i]=mp[3][ori(x,y)][i]=0,ma[x][y]=0;
}
int main()
{
for(int i=1;i<=9;q[i]=i,i++)for(int j=1;j<=9;j++)scanf("%d",&ma[i][j]),
cnt[i]+=mp[1][i][ma[i][j]]=mp[2][j][ma[i][j]]=mp[3][ori(i,j)][ma[i][j]]=(ma[i][j]>0);
sort(q+1,q+1+9,cmp);for(int i=0;i<=9;i++)nx[q[i]]=q[i+1];
dfs(nx[0],1);
printf("%d",ans);
}
P1514 引水入城
论少加一个一的后果
无法做到的情况挺好判断的,直接让第一行都建蓄水厂
如果做得到,可以发现第一行的蓄水厂最后影响到最后一行的范围应该是一段连续的区间(如果区间中有无法达到的点,那么其它点也一定无法达到
所以把第一行每个位置建蓄水厂能影响的范围求出来,之后就相当于几个区间合并,但这些区间不要求相交,只要能覆盖完整个区域就好了
还要加一个剪枝,如果第一行的一个城市能被左右两个城市输送水,即高度小于左右两个,它就一定不需要建蓄水厂。
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
queue<int>q;
struct node{int l,r;}p[505];
int n,m,ans,cnt,h[505][505],vis[505][505];
bool cmp(node a,node b){return a.l==b.l?a.r>b.r:a.l<b.l;}
int tri()
{
while(!q.empty())
{
int nw=q.front(),x=(nw-1)/m+1,y=(nw-1)%m+1;q.pop();
if(!vis[x+1][y]&&x+1&&y&&x+1<=n&&y<=m&&h[x][y]>h[x+1][y])vis[x+1][y]=1,q.push((x+1-1)*m+y);
if(!vis[x][y+1]&&x&&y+1&&x<=n&&y+1<=m&&h[x][y]>h[x][y+1])vis[x][y+1]=1,q.push((x-1)*m+y+1);
if(!vis[x-1][y]&&x-1&&y&&x-1<=n&&y<=m&&h[x][y]>h[x-1][y])vis[x-1][y]=1,q.push((x-1-1)*m+y);
if(!vis[x][y-1]&&x&&y-1&&x<=n&&y-1<=m&&h[x][y]>h[x][y-1])vis[x][y-1]=1,q.push((x-1)*m+y-1);
}
int flag=1;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)flag&=vis[i][j];
return flag;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&h[i][j]);
for(int i=1;i<=m;i++)vis[1][i]=1,q.push(i);
if(tri())
{
for(int i=1;i<=m;i++)
if(h[1][i]>=h[1][i-1]&&h[1][i]>=h[1][i+1])
{
memset(vis,0,sizeof vis);
vis[1][i]=1;q.push(i);tri();cnt++;
for(int i=1;i<=m;i++){if(!vis[n][i-1]&&vis[n][i])p[cnt].l=i;if(vis[n][i]&&!vis[n][i+1])p[cnt].r=i;}
}
sort(p+1,p+1+cnt,cmp);int r=0;
for(int i=1,mx=1;i<=cnt;i++)
{
if(p[i].l>r+1)r=p[mx].r,ans++;
if(p[mx].r<p[i].r)mx=i;
}
printf("1\n%d\n",ans+(r<m));
}
else {for(int j=1;j<=m;j++)ans+=vis[n][j]==0;printf("0\n%d",ans);return 0;}
}
P1312 Mayan游戏
仍然是我不会的爆搜模拟(不行啊以后要去做游戏的话怎么办啊
可以发现跟左交换和跟右交换是可以相互替代的,除非交换的时候有空的方格
把这个剪枝掉就贼快了。
其实还有一些地方写得丑,比如降落的时候可以记录前面出现的空格数,进而得知该下降多少格,做到扫一遍就行
还有就是删除的时候如果没有被删的可以直接返回
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,h[10],flag;int mat[10][10][10],op[10][5],vis[10][10],sta[10];
void down(int len)
{
for(int i=1,en=0;i<=5;en=0,i++)
{
for(int j=1;j<=7;mat[len][i][j]=0,j++)
if(mat[len][i][j])sta[++en]=mat[len][i][j];
for(int j=1;j<=en;j++)mat[len][i][j]=sta[j];
}
}
int dele(int len)
{
memset(vis,0,sizeof vis);int flag=0;
for(int x=1;x<=5;x++)for(int y=1;y<=7;y++)
{
if(mat[len][x][y]&&mat[len][x][y]==mat[len][x-1][y]&&mat[len][x][y]==mat[len][x+1][y])
vis[x][y]=vis[x-1][y]=vis[x+1][y]=1;
if(mat[len][x][y]&&mat[len][x][y]==mat[len][x][y-1]&&mat[len][x][y]==mat[len][x][y+1])
vis[x][y]=vis[x][y-1]=vis[x][y+1]=1;
}
for(int x=1;x<=5;x++)for(int y=1;y<=7;y++)if(vis[x][y])mat[len][x][y]=0,flag=1;return flag;
}
void dfs(int len)
{
if(len>n)
{
flag=1;
for(int i=1;i<=5;i++)flag&=(mat[len-1][i][1]==0);
if(flag)for(int i=1;i<=n;i++)printf("%d %d %d\n",op[i][1]-1,op[i][2]-1,op[i][3]);
return;
}
for(int x=1;x<=5&&!flag;x++)for(int y=1;y<=7&&!flag;y++)if(mat[len-1][x][y])
{
if(x+1<=5&&!flag)
{
memcpy(mat[len],mat[len-1],sizeof mat[len-1]);
swap(mat[len][x][y],mat[len][x+1][y]);down(len);
while(dele(len))down(len);op[len][1]=x;op[len][2]=y;op[len][3]=1;dfs(len+1);
}
if(x-1>=1&&!flag&&!mat[len-1][x-1][y])
{
memcpy(mat[len],mat[len-1],sizeof mat[len-1]);
swap(mat[len][x][y],mat[len][x-1][y]);down(len);
while(dele(len))down(len);op[len][1]=x;op[len][2]=y;op[len][3]=-1;dfs(len+1);
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1,a;i<=5;i++)for(;scanf("%d",&a),a!=0;mat[0][i][++h[i]]=a);
dfs(1);if(!flag)puts("-1");return 0;
}
P1315 观光公交
玄学贪心。
假设路径i之后第一个汽车到达时间先于乘客到达时间的点为j
那么在路径i上放置一个加速器的贡献,就是目的地在从i到j之间的人数(感性理解一下
然后我就迷茫了
于是每放置一次加速器,就重新计算一下每个路径的贡献,每次贪心选贡献最大的那条路径进行放置。
为什么要重新计算呢?因为每次放置会改变汽车到达目的地的时间。
尤其注意$k=0$的情况。。。。。。
以及当长度为0时是无法继续放加速器的(我™写了个大于等于0调了半天
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1005;
int st[maxn*10][5],tp[maxn];long long ans;
int n,m,k,d[maxn],v[maxn],dis[maxn],las[maxn],sum[maxn];
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<n;i++)scanf("%d",&dis[i]);
for(int i=1;i<=m;i++)scanf("%d%d%d",&st[i][0],&st[i][1],&st[i][2]),
las[st[i][1]]=max(las[st[i][1]],st[i][0]),v[st[i][2]]++;
for(int i=1;i<=n+1;i++)sum[i]=v[i]+sum[i-1];
d[1]=0;
for(int i=1;i<=n;d[i+1]=d[i]+dis[i],i++)
if(d[i]>las[i])tp[i]=1;else d[i]=las[i],tp[i]=0;
while(k--)
{
int mx=0,nx=0;
for(int i=n-1,pre=n;i;pre=tp[i]?pre:i,i--)
if(mx<sum[pre]-sum[i]&&dis[i]>=1)mx=sum[pre]-sum[nx=i];
if(!nx)break;dis[nx]--;
d[1]=0;
for(int i=1;i<=n;d[i+1]=d[i]+dis[i],i++)
if(d[i]>las[i])tp[i]=1;else d[i]=las[i],tp[i]=0;
}
for(int i=1;i<=m;i++)ans+=abs(d[st[i][2]-1]+dis[st[i][2]-1]-st[i][0]);
printf("%lld\n",ans);return 0;
}
题目渐渐变得难起来。。。。。
P1081 开车旅行
考虑$n^2$的暴力就是模拟,一步一步走就好了
但是注意到在每个点时,最多只有两种走法,而且走法固定,所以考虑倍增
所以首先就是要初始化倍增,在不超时的情况下得出每一个点的走法
这个可以用set做,也可以用双向链表
用双向链表做,先不考虑往前还是往后,直接排序,在i-2,i-1,i+1,i+2这4个位置找最小值和次小值
考虑往前还是往后的话,可先考虑第一个城市的i-2,i-1,i+1,i+2,此时找到的最小值肯定是向右走的,
然后把第一个城市从双向链表中删去,第二个城市就变成了新的第一个城市,以此类推
倍增的时候,最好把两人各走一次当做一步,方便讨论,再开两个数组,一个存下A走的距离,一个存下B走的距离
Code
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
long long A[maxn][25],B[maxn][25],sum1,sum2,X;
int ans,nx[maxn],pre[maxn],mn[maxn][2],f[maxn][25];
int n,m,nw,q[maxn],h[maxn],rk[maxn],sta[10];bool cmp(int a,int b){return h[a]<h[b];}
bool cmp1(int a,int b){return abs(h[a]-h[nw])==abs(h[b]-h[nw])?h[a]<h[b]:abs(h[a]-h[nw])<abs(h[b]-h[nw]);}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&h[i]),q[i]=i;sort(q+1,q+1+n,cmp);
for(int i=1;i<=n;i++)rk[q[i]]=i,pre[i]=i-1,nx[i]=i+1;nx[n]=0;nw=1;
for(int i=1,en=0;i<=n;i++,en=0,nw=i)
{
if(pre[rk[i]])sta[++en]=q[pre[rk[i]]];
if(pre[pre[rk[i]]])sta[++en]=q[pre[pre[rk[i]]]];
if(nx[rk[i]])sta[++en]=q[nx[rk[i]]];
if(nx[nx[rk[i]]])sta[++en]=q[nx[nx[rk[i]]]];
sort(sta+1,sta+1+en,cmp1);
if(pre[rk[i]])nx[pre[rk[i]]]=nx[rk[i]];
if(nx[rk[i]])pre[nx[rk[i]]]=pre[rk[i]];
if(en>=1)mn[i][0]=sta[1];if(en>=2)mn[i][1]=sta[2];
}
for(int i=1;i<=n;i++)f[i][0]=mn[mn[i][1]][0],A[i][0]=abs(h[i]-h[mn[i][1]]),B[i][0]=abs(h[mn[i][1]]-h[mn[mn[i][1]][0]]);
for(int k=1;k<=20;k++)for(int i=1;i<=n;i++)
f[i][k]=f[f[i][k-1]][k-1],A[i][k]=A[i][k-1]+A[f[i][k-1]][k-1],B[i][k]=B[i][k-1]+B[f[i][k-1]][k-1];
scanf("%lld%d",&X,&m);
for(int i=1;i<=n;i++)
{
int x=i;long long sumA=0,sumB=0,tot=X;
for(int k=20;k>=0;k--)if(f[x][k]&&tot>=A[x][k]+B[x][k])
tot-=A[x][k]+B[x][k],sumA+=A[x][k],sumB+=B[x][k],x=f[x][k];
if(mn[x][1]&&tot>=A[x][0])sumA+=A[x][0];
if(sumB==0)ans=((!sum2)&&h[i]>h[ans])||(!ans)?i:ans;
else if((sumA*sum2<sum1*sumB)||(sumA*sum2==sum1*sumB&&h[i]>h[ans])||!ans)ans=i,sum1=sumA,sum2=sumB;
}
printf("%d\n",ans);
for(int i=1,x,tot;i<=m;i++)
{
scanf("%d%d",&x,&tot);
long long sumA=0,sumB=0;
for(int k=20;k>=0;k--)if(f[x][k]&&tot>=A[x][k]+B[x][k])
tot-=A[x][k]+B[x][k],sumA+=A[x][k],sumB+=B[x][k],x=f[x][k];
if(mn[x][1]&&tot>=A[x][0])sumA+=A[x][0];
printf("%lld %lld\n",sumA,sumB);
}
}