- Leaking Roof 签到
大意:给定 n*n 格子,每个格子的水量为m, 当前格子的水会向周围有公共边的,且高度严格大于的格子等量流动,当且仅当高度为0时水会流出,求最终每个格子流出的水量。n<=500
思路:高度最高的格子只会往别的格子流水,直接按高度从小到大排序,模拟就好了。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=510;
typedef pair<int,int> PII;
int n,m,k;
struct node{
int h,x,y;
bool operator <(const node &W)const
{
return h>W.h;
}
}q[N*N];
int H[N][N];
double ans[N][N];
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%d",&H[i][j]);
q[k++]={H[i][j],i,j};
ans[i][j]=m;
}
sort(q,q+k);
for(int i=0;i<k;i++)
{
int h=q[i].h,x=q[i].x,y=q[i].y;
int cnt=0;
vector<PII> tmp;
for(int j=0;j<4;j++)
{
int a=x+dx[j],b=y+dy[j];
if(a<1||a>n||b<1||b>n)continue;
if(h>H[a][b])cnt++,tmp.push_back({a,b});
}
if(cnt)
{
double fl=ans[x][y]/cnt;
for(auto t:tmp)
{
int a=t.first,b=t.second;
ans[x][y]-=fl;
ans[a][b]+=fl;
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(H[i][j])printf("0 ");
else printf("%.6f ",ans[i][j]);
}
puts("");
}
return 0;
}
- Addition 二进制、签到
大意:给定一个数 n,代表二进制的位数,sgn 代表符号 a[],b[]代表对应二进制位的数字, ∑ i n a [ i ] s g n [ i ] ∗ 2 i \sum_i^na[i]sgn[i]*2^i ∑ina[i]sgn[i]∗2i
求出 相加后的结果,按上面的形式输出。30<=n<=60
思路:题目保证了一定有解,所以直接模拟就行,不用担心题目会无解。比较迷惑性的就是 sgn 符号位,如果不考虑符号位,直接模拟竖式运算求结果就行了。考虑符号其实还是竖式运算,只不过原先只有进位,这里有了符号相当于可能出现借位,模拟就好了。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=65;
typedef long long LL;
int sgn[N],a[N],b[N],c[N];
int n;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=0;i<n;i++)cin>>sgn[i];
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)cin>>b[i];
int f=0;//纪录进位
for(int i=0;i<n;i++)
{
int t=a[i]+b[i]+f*sgn[i];//若当前位是负号,从后面来的进1,相当于在这里是要减去的
if(t==-1)c[i]=1,f=-1;
else c[i]=t%2,f=t/2;
f*=sgn[i];//当前位是负号的话,进1,相当于多减1,也就是对前面的借位,
}
for(int i=0;i<n;i++)
{
cout<<c[i];
if(i!=n-1)cout<<" ";
}
return 0;
}
总结:很多东西是具有相同的本质的,处理不熟悉的我们可以类比成熟悉的去处理
- Sort 思维、技巧
大意:给定一个长度为 n 的序列,以及 k,问每次将序列 a 按顺序分成不超多 k 段,是否能经过若干次操作使得序列 a 单调不降。若不能输出 -2,若可以但所需的最少操作次数超过 3n 次,输出 -1。否则输出操作次数,每次操作的分段方式以及每段的组合方式(n<=30000)
思路: 因为每次操作分成的段数受 k 的限制,我们从 k 的角度入手,
显然当 k 为 1时,如果序列 a 不满足条件的话就是不行的。
当 k=2时,每次分成两段,如果不交换两段的顺序相当于没变,每轮交换两段的位置实际上相当于对序列进行右移操作,并且只有一种情况可以通过平移变成单调不降的序列即类似于“567123”这样的。
当k>=3时,我们可以证明一定可以通过不超过3n次操作将序列变成单调不降,很容易证:我们每次从未排序的序列中找到最小的作为一段的开头,已经排好序的作为一段,剩下的作为另一段,即我们每次可以通过分成不超过三段最多进行 n 次使得a序列变成单调不降。对于k=1和k=2的时候我们很容易实现,主要就是k>=3时的情况。暴力查找、平移什么的复杂度直接爆炸,我们可以考虑将所有相同的数分到一组,然后对每组进行操作,因为要分段,所以得知道每个数的下标,所以我们按大小关系,将下标进行分组,并且用树状数组维护开头有多少个数是完成排序的,这样我们就可以知道任意一轮操作当前数的序列中的下标了。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,a[N],b[N],tot,_;
int tr[N];
vector<int> g[N],ans;
void add(int x,int y){while(x<=n)tr[x]+=y,x+=x&-x;}
int ask(int x){int res=0;while(x)res+=tr[x],x-=x&-x;return res;};
bool check()
{
for(int i=1;i<n;i++)if(a[i]>a[i+1])return 0;
return 1;
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
if(check())
{
cout<<'0';
if(_)cout<<"\n";
return ;
}
if(m==1)cout<<-2;
else if(m==2)
{
int pre=1;
while(pre<n&&a[pre]<=a[pre+1])pre++;
int suf=pre+1;
while(suf<n&&a[suf]<=a[suf+1])suf++;
if(suf==n&&a[n]<=a[1])
{
cout<<"1\n"<<"2\n";
cout<<0<<" "<<pre<<" "<<n<<"\n";
cout<<"2 1";
}
else cout<<-2;
}
else
{
for(int i=0;i<=n;i++)tr[i]=0,g[i].clear();
ans.clear();
sort(b+1,b+1+n);
tot=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
{
int pos=lower_bound(b+1,b+1+tot,a[i])-b;
g[pos].push_back(i);
}
for(int i=tot;i;i--)
{
for(auto v:g[i])
{
int pos=v;
pos+=ask(pos);//因为是从后往前更新的,新的编号就是之前的编号加上加到开头的个数
if(pos==1)continue;
ans.push_back(pos);
add(v,-1);
add(1,1);
}
}
cout<<ans.size()<<"\n";
int sz=ans.size();
for(auto v:ans)
{
sz--;
if(v==n)
{
cout<<"2\n"<<0<<" "<<v-1<<" "<<v<<"\n";
cout<<"2 1";
}
else
{
cout<<"3\n"<<0<<" "<<v-1<<" "<<v<<" "<<n<<"\n";
cout<<"2 1 3";
}
if(sz)cout<<"\n";
}
}
if(_)cout<<"\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>_;
while(_--)solve();
return 0;
}
总结:思考问题的时候,不要总想这一下子把所有问题想出,把一个大的问题分成若干个小问题,一步一步思考。
- Leapfrog 贪心、dp、思维、问题划分
大意:给定 n 个跳蛙,每个跳蛙有个初始得分 a i a_i ai 以及若干个 ( b i , t i ) (b_i,t_i) (bi,ti),初始时候只有一个。每次可以任选一个跳蛙死亡,并按顺序进行完该跳蛙所有的的 ( b i , t i ) (b_i,t_i) (bi,ti) ,对于每个 ( b i , t i ) (b_i,t_i) (bi,ti) 是进行 t i t_i ti 操作,每次任选一个存活的跳蛙,将它的分值加 b i b_i bi ,并把在该跳蛙后面加上一个 ( b i , t i ) (b_i,t_i) (bi,ti)。问最终剩下的那个跳蛙的最大值。 ( 1 < = n < = 1 e 5 , 1 < = a i , b i < = 1 e 4 , t i = 2 或 3 ) (1<=n<=1e5, 1<=a_i,b_i<=1e4,t_i=2或3) (1<=n<=1e5,1<=ai,bi<=1e4,ti=2或3)
思路:通过读题我们发现,其实对于某个回合选择去死亡的跳蛙,对于它后面的 ( b i , t i ) (b_i,t_i) (bi