过了一个星期才写博客,可以看出我有多懒。
本场做了三题,总算签到成功了。
-
A. RUN
题目链接:https://www.nowcoder.com/acm/contest/140/A
题意:一个人在一秒钟内可以选择往前走1米,或往前跑k米,但不能连续跑。给出q组询问,每组询问有一个[l,r]区间,求解到l到r区间的每一个距离共有多少种走法。
题解:刚开始看漏了“连续”这两个字,一直wa。
可以用dp[i][1/0]表示到第i米的这一步是跑的还是走的。
dp[i][1] = dp[i-k][0],因为如果这一步是跑的,从第i-k米到第i米只能是走的;
dp[i][0] = dp[i-i][0]+dp[i-1][1],如果这一步是走的,那么上一步随便走还是跑。
最后记得要维护前缀和- -
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
const int mo = 1000000007;
long long q,k;
const int n = 100000;
long long dp[maxn][2],sum[maxn];
int main()
{
cin>>q>>k;
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
sum[0] = 0;
for(int i=1;i<=n;i++)
{
if(i>=k) dp[i][1] = dp[i-k][0];
dp[i][0] = (dp[i-1][0] + dp[i-1][1])%mo;
sum[i] = sum[i-1] + (dp[i][0] + dp[i][1])%mo; //维护一下前缀和
}
for(int i=0;i<q;i++)
{
int l,r;
scanf("%d%d",&l,&r);
cout<<((sum[r]-sum[l-1])%mo+mo)%mo<<endl; //咳咳,取模注意一下
}
return 0;
}
-
D. money
题目链接:https://www.nowcoder.com/acm/contest/140/D
题目大意:有n个小镇,每个小镇有一个价格k,可以将商品卖出k 或者 买入一个价格为k的商品(默认商品全都一毛一样),求经过n个小镇后最大收益是多少,并且在得到最大收益的同时要让交易次数尽可能的少,问满足条件后的最少交易次数是?
题解:比赛时这题建模很快,思路比较清晰,就是将每个小镇的价格画成曲线图,求有多少个上升段,并且只在上升段的两个端点购买,1A。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
bool flag;
int t,n;
long long ans,tot;
long long a[maxn],p[maxn];
int main()
{
scanf("%d",&t);
while(t--)
{
ans = 0;
tot = 0;
memset(a,0,sizeof(a));
memset(p,0,sizeof(p));
n = read();
for(int i=0;i<n;i++)
a[i] = read();
flag = 0; //flag标记之前一段是不是上升段
for(int i=1;i<n;i++)
{
if(a[i]>a[i-1]) //如果当前段是上升段,自然要把收益给加上去
{
flag = 1;
ans += a[i]-a[i-1];
}
else if(a[i]<a[i-1]){
if(flag) { //由于这一段是下降段,flag标记这之前一段是不是上升段,如果是的话,交易次数+2
tot+=2;
}
flag = 0;
}
}
if(flag)
tot+=2; //如果是以上升段结束,那么最后还要+2
cout<<ans<<" "<<tot<<endl;
}
return 0;
}
思路2:用DP做,这题同样也可以转化为背包问题的一种。
用f[i][1/0]表示到了第i个小镇之后,身上是否有商品,用于维护最大利益;
g[i][1/0]则用来维护最小的交易次数。。具体内容看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
typedef long long ll;
ll n,a[maxn],t,f[maxn][2],g[maxn][2];
const int inf = 0x3f3f3f3f;
int main()
{
cin>>t;
while(t--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
f[1][1] = -a[1];
g[1][1] = 1;
for(int i=2;i<=n;i++)
{
if(f[i-1][0]>f[i-1][1]+a[i]) //f[i-1][0]表示在第i-1个小镇空手,f[i-1][1]+a[i]表示把东西在第i个小镇卖掉
{
f[i][0] = f[i-1][0];
g[i][0] = g[i-1][0];
}
else if(f[i-1][0]<f[i-1][1]+a[i])
{
f[i][0] = f[i-1][1]+a[i];
g[i][0] = g[i-1][1]+1;
}
else //如果两种收益一样的话,取交易次数少的
{
f[i][0] = f[i-1][0];
g[i][0] = min(g[i-1][0],g[i-1][1]+1);
}
if(f[i-1][1]<f[i-1][0]-a[i]) //f[i-1][1]表示东西到第i个地方不卖,f[i-1][0]-a[i]表示空手到第i个小镇买东西
{
f[i][1] = f[i-1][0]-a[i];
g[i][1] = g[i-1][0]+1;
}
else if(f[i-1][1]>f[i-1][0]-a[i])
{
f[i][1] = f[i-1][1];
g[i][1] = g[i-1][1];
}
else
{
f[i][1] = f[i-1][1];
g[i][1] = min(g[i-1][0]+1,g[i-1][1]);
}
}
ll ans1,ans2;
if(f[n][1]>f[n][0]) //结果也是三个类比的过程
{
ans1 = f[n][1];
ans2 = g[n][1];
}
else if(f[n][1]<f[n][0])
{
ans1 = f[n][0];
ans2 = g[n][0];
}
else
{
ans1 = f[n][1];
ans2 = min(g[n][1],g[n][0]);
}
cout<<ans1<<" "<<ans2<<endl;
}
return 0;
}
-
I.Car
题目链接:https://www.nowcoder.com/acm/contest/140/I
题意:
有一张n×n的格子图,要求在矩阵的边界处放上若干辆小车,小车只能向矩阵的对面行驶(不能沿着边界行驶),道路中还有m个 障碍,小车不能经过障碍,两两小车不能相碰,问最大方法。
题解 :
首先推出车辆的最大放法的规律(倪大佬推的),然后可以得出没有障碍物的规律。
加上障碍物后,我的第一感觉是如果(i,j)位置有障碍物,那么第i行和第j列都不能放车了,所以结果会-2。
考虑到上面一点已经有80分了,在考虑特例的时候发现,如果n是奇数,那么图正中间的十字有障碍的话,结果只会-1。这点刚开始写if语句的时候写错了,被发现后就A了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 100005;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m;
bool visx[maxn],visy[maxn];
int a[maxn];
int main()
{
a[1] = 1;
a[2] = 4;
for(int i=3;i<maxn;i++)
{
if(i%2!=0)
a[i] = a[i-1]+1;
else
a[i] = i*2;
}
while(~scanf("%d%d",&n,&m))
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
int ans = a[n];
for(int i=0;i<m;i++)
{
int x,y;
x = read();
y = read();
if(n%2==0) {
if(!visx[x])
ans--;
if(!visy[y])
ans--;
visx[x] = 1;
visy[y] = 1;
}
if(n%2!=0) {
if(x==n/2+1||y==n/2+1)
{
if(!visx[x]||!visy[y])
{
ans--;
visx[x] = 1;
visy[y] = 1;
}
}
else {
if(!visx[x])
ans--;
if(!visy[y])
ans--;
visx[x] = 1;
visy[y] = 1;
}
}
}
cout<<ans<<endl;
}
return 0;
}
-
J. Farm
题目链接:https://www.nowcoder.com/acm/contest/140/J
题目大意:有一个n×m的农场,每一个格子只能施某种特定的肥料,否则就会死掉。
首先给出每块格子能施什么肥,然后给出t个修改,每个修改表示以(x1,y1)为左上角,(x2,y2)为右下角的格子都施k肥,问t次操作后总共有多少块格子会死亡。
题解:
需要了解一下矩阵前缀和这个东西- -可以由线段的前缀和推出来,我这里就不写了。
题目思路1:
采用随机化的做法。假设我们以v[i][j]记录每块格子能施什么肥,cnt[i][j]记录t次操作后这块格子施了多少次肥,f[i][j]表示t次操作后这一块的值。如果t次操作后,某块格子f[i][j]!=cnt[i][j]*v[i][j],说明这个格子施了它不该有的肥料,ans++。当然了,这是会有意外情况的,如果一块格子先施了肥料1,又施了肥料3,恰巧v[i][j]=2,那就结果错误了。 所以,运用这个算法前,先要将每种肥料都随机化一遍。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1000005;
inline void read(int &x)
{
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
inline void print(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
ll n,m,t,has[1000005];
vector<ll> v[maxn],cnt[maxn],f[maxn]; //cnt维护被更新的次数,f维护T次更新后的值,v维护初始值
int main()
{
cin>>n>>m>>t;
for(int i=0;i<=n+1;i++)
{
v[i].resize(m+5);
cnt[i].resize(m+5);
f[i].resize(m+5);
}
for(int i=1;i<=n*m;i++)
{
has[i] = i;
}
random_shuffle(has+1,has+1+m*n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
ll x;
scanf("%lld",&x);
v[i][j] = has[x];
cnt[i][j] = 0;
f[i][j] = 0;
}
}
while(t--)
{
int x1,x2,y1,y2,k;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&k);
f[x1][y1] += has[k];
f[x2+1][y2+1] += has[k];
f[x1][y2+1] -= has[k];
f[x2+1][y1] -= has[k];
cnt[x1][y1]++;
cnt[x2+1][y2+1]++;
cnt[x1][y2+1]--;
cnt[x2+1][y1]--;
}
ll ans = 0;
for(int i=1;i<=n;i++) //这是求矩阵前缀和
{
for(int j=1;j<=m;j++)
{
f[i][j] += f[i-1][j] + f[i][j-1] - f[i-1][j-1];
cnt[i][j] += cnt[i-1][j] + cnt[i][j-1] - cnt[i-1][j-1];
if(f[i][j]!=cnt[i][j]*v[i][j])
ans++;
}
}
cout<<ans<<endl;
return 0;
}
题目思路2:
用二维BIT做。
大致想法还是差不多的,树状数组只是用来求和。
在读入每个块应该施的肥料的时候用一个小技巧,把运用肥料k的所有块都用vector保存下来,同理,在后面施肥的时候也是将运用肥料k的区域记录下来。
首先把t次施肥全施了,然后遍历肥料的种数。从1到m×n,对待每种肥料,如果有的地应该施这种肥料,那么就去掉在t次施肥中施这种肥料,然后看这些地上是否还有其他肥料,看完后再把之前去掉的加上去。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
int n,m,t,ans;
struct node{
int a,b,c,d;
};
vector<pair<int,int> >point[maxn];
vector<node>p[maxn];
vector<int> tree[maxn];
inline void read(int &x)
{
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
inline int lowbit(int x)
{
return x & -x;
}
inline void add(int x,int y,int val)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
tree[i][j] += val;
}
inline int getsum(int x,int y)
{
int ans = 0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
ans += tree[i][j];
return ans;
}
inline void change(int x1,int y1,int x2,int y2,int val) //矩阵前缀和
{
add(x1,y1,val);
add(x2+1,y2+1,val);
add(x1,y2+1,-val);
add(x2+1,y1,-val);
}
int main()
{
read(n),read(m),read(t);
for(int i=1;i<=n;i++)
tree[i].resize(m+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int k;
scanf("%d",&k);
point[k].push_back(make_pair(i,j));
}
while(t--)
{
int x1,x2,y1,y2,k;
read(x1),read(y1),read(x2),read(y2),read(k);
p[k].push_back(node{x1,y1,x2,y2});
change(x1,y1,x2,y2,1);
}
int ans = 0;
for(int i=1;i<=n*m;i++)
{
if(point[i].size()) //如果有的土地适合第i种肥料
{
for(auto j:p[i]) //把T次操作中所有施第i种肥料的区域先复原
{
change(j.a,j.b,j.c,j.d,-1);
}
for(auto j:point[i]) //如果复原后不等于0,说明存在不止第i种肥料这1种肥料。
{
if(getsum(j.first,j.second))
ans++;
}
for(auto j:p[i]) //复原上述操作
{
change(j.a,j.b,j.c,j.d,1);
}
}
}
cout<<ans<<endl;
return 0;
}
二维BIT相对于做法1,采用树状数组的方法求矩阵前缀和。