集训模拟赛12
前言
今天虎哥出题变简单了,今天本来可以写对三个题,但是\(T2\)由于没看题目,改了一个地方就过了,\(T3\)还把数组开小了,导致最后少拿了超级多的分。现在后悔也没用了,吸取教训吧,看题:
NO.1 狡猾的商人
该题与题库中\(How\ Many\ Answers\ Are\ Wrong\)是一样的。
题目描述
刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。
账本上记录了\(n\)个月以来的收入情况,其中第\(i\)个月的收入额为\(A_i(i=1,2,3…n-1,n)\)。当 \(A_i\)大于\(0\)时表示这个月盈利\(A_i\) 元,当 \(A_i\)小于\(0\)时表示这个月亏损\(A_i\) 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。
刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,刁姹总共偷看了\(m\)次账本,当然也就记住了\(m\)段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。
输入格式
第一行为一个正整数\(w\),其中\(w < 100\),表示有\(w\)组数据,即\(w\)个账本,需要你判断。
每组数据的第一行为两个正整数\(n\)和\(m\),其中\(n < 100,m < 1000\),分别表示对应的账本记录了多少个月的收入情况以及偷看了多少次账本。
接下来的\(m\)行表示刁姹偷看\(m\)次账本后记住的\(m\)条信息,每条信息占一行,有三个整数\(s\),\(t\)和\(v\),表示从第\(s\)个月到第\(t\)个月(包含第\(t\)个月)的总收入为\(v\),这里假设\(s\)总是小于等于\(t\)。
输出格式
包含\(w\)行,每行是\(true\)或\(false\)。
其中第\(i\)行为\(true\)当且仅当第\(i\)组数据,即第\(i\)个账本不是假的;第\(i\)行为\(false\)当且仅当第i组数据,即第\(i\)个账本是假的。
样例
样例输入
2
3 3
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51
样例输出
true
false
分析
这个题其实就是带权并查集,每一次把不同父亲区间的值改变,然后与之前进行比较,如果不符就是\(false\),否则就是\(true\)
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int n,m;
int flag;
int fa[maxn],dis[maxn];
int Find(int x){
if(x == fa[x])return x;
int fu = fa[x];
fa[x] = Find(fa[x]);
dis[x] += dis[fu];
return fa[x];
}
void Init(){
for(int i=0;i<maxn;++i){
fa[i] = i;
dis[i] = 0;
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
Init();
flag=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
a--;
int x = Find(a);
int y = Find(b);
if(x == y){
if(dis[b]-dis[a] != c){
flag = 1;
}
}
else{
fa[y] = x;
dis[y] = c-dis[b]+dis[a];
}
}
if(flag == 0)printf("true\n");
else printf("false\n");
}
return 0;
}
NO.2 MICE
题目
\(S\)国的动物园是一个\(N\times M\)的网格图,左上角的坐标是\((1,1)\),右下角的坐标是\((N,M)\)。小象在动物园的左上角,它想回到右下角的家里去睡觉,但是动物园中有一些老鼠,而小象又很害怕老鼠。动物园里的老鼠是彼此互不相同的。小象的害怕值定义为他回家的路径上可以看见的不同的老鼠的数量。若小象当前的位置为\((x1,y1)\),小象可以看见老鼠,当且仅当老鼠的位置\((x2,y2)\)满足\(|x1-x2|+|y1-y2|\le 1\)。由于小象很困了,所以小象只会走一条最近的路回家,即小象只会向下或者向右走。现在你需要帮小象确定一条回家的路线,使得小象的害怕值最小。
输入格式
第一行包含两个用空格隔开的整数,\(N\)和\(M\)。
接下来一个\(N\times M\)的矩阵表示动物园的地图。其中\(A[i][j]\)表示第\(i\)行第\(j\)列上老鼠的数量。 若\(A[i][j]=0\)则表示当前位置上没有老鼠(小象的家里也可能存在老鼠)。
输出格式
输出一个整数,表示路线最小的害怕值是多少。
样例
样例输入
3 9
0 0 1 0 0 0 0 0 1
1 1 1 1 1 1 0 1 0
1 0 0 1 0 0 1 0 0
样例输出
9
数据范围与提示
对于\(10\%\)的数据,\(1\le N,M\le 5\)。
对于\(100\%\)的数据,\(1\le N,M\le 1000,0\le A_{i,j}\le 100\)。
分析
经典的一个\(dp\)模型,这个题中给出了小象的移动方向,我们就可以预处理每个点能够看到的不同种老鼠数量,然后进行状态转移,首先定义状态数组为\(f[i][j]\)是到\(i\)行\(j\)列的害怕值。
题目中给出了小象会害怕的一个范围:
\]
我们就可以根据这个推出来只有老鼠在小象的上下左右时才会害怕,然后开始预处理。
但是预处理也是有点门道的,我在开始预处理的时候想的是在当前点上下左右的都加起来,但是写完以后发现答案要大好多,仔细分析一下,我们可以发现几个特点:
\(1\)、当前点由左边或上边转移而来,所以左边或上边已经能够看到当前点的左边和上边的点,所以只需要预处理该点下和右边的数量。
\(2\)、当前点是从左或上转移而来,如果上边的点仍然从上边转移来,那么该点的左边一定是没有统计过的,而从左边转移来的点上一次转移还是左边也是一样的,该点上边的点一定没有统计,所以我们就需要第三维来记录从左还是上转移而来。数组也就变成了\(f[i][j][1]\),和\(f[i][j][0]\),分别表示从左边来还是从右边来。如果一直从左转移,那么需要加上上边的害怕值,从上同理。
那么我们这样就得到了状态转移方程和预处理。预处理\(val[i][j]\)数组表示第\(i\)行\(j\)列会得到的害怕值,根据上边的分析得到只需要看这个点右边和下边即可。然后就是状态转移方程:
\]
\]
最后只需要比较\(f[i][j][1]\)和\(f[i][j][0]\)的最小值就好了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int a[maxn][maxn],f[maxn][maxn][3];
int val[maxn][maxn];
int n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<=n;++i){
for(int j=0;j<=m;++j){
if(a[i][j+1] != 0)val[i][j]+=a[i][j+1];//加上右边的老鼠
if(a[i+1][j] != 0)val[i][j]+=a[i+1][j];加上下边的老鼠
}
}
memset(f,0x3f,sizeof(f));//初始化极大值
f[1][0][0] = f[1][0][1] = val[1][0];//边界初始化
f[0][1][0] = f[0][1][1] = val[0][1];
f[1][1][0] = f[1][1][1] = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
f[i][j][1] = min(f[i][j-1][1]+val[i][j]+a[i-1][j],f[i][j-1][0]+val[i][j]);//从左转移来
f[i][j][0] = min(f[i-1][j][1]+val[i][j],f[i-1][j][0]+val[i][j]+a[i][j-1]);//从上转移来
}
}
int ans = min(f[n][m][1],f[n][m][0]);//统计最小值
printf("%d\n",ans);
return 0;
}
NO.3 道路与航线
题目描述
\(Farmer\ John\)正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到\(T\)个城镇 \((1 \le T \le 25,000)\),编号为\(1\to T\)。这些城镇之间通过\(R\)条道路 \((1 \le R \le 50,000\),编号为\(1\)到\(R)\) 和\(P\)条航线 \((1 \le P \le 50,000,\)编号为\(1\)到\(P)\) 连接。每条道路\(i\)或者航线\(i\)连接城镇\(A_i (1 \le A_i \le T)\)到\(B_i (1 \le B_i \le T)\),花费为\(C_i\)。对于道路,\(0 \le C_i \le 10,000\);然而航线的花费很神奇,花费\(C_i\)可能是负数\((-10,000 <= C_i <= 10,000)\)。道路是双向的,可以从\(A_i\)到\(B_i\),也可以从\(B_i\)到\(A_i\),花费都是\(C_i\)。然而航线与之不同,只可以从\(A_i\)到\(B_i\)。事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台 了一些政策保证:如果有一条航线可以从\(A_i\)到\(B_i\),那么保证不可能通过一些道路和航线从\(B_i\)回到\(A_i\)。由于\(FJ\)的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇\(S(1 <= S <= T)\) 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。
输入格式
第\(1\)行:四个空格隔开的整数: \(T, R, P, and S\)
第\(2\)到\(R+1\)行:三个空格隔开的整数(表示一条道路):\(A_i, B_i\) 和 \(C_i\) 第\(R+2\)到\(R+P+1\)行:三个空格隔开的整数(表示一条航线):\(A_i, B_i\) 和 \(C_i\)
输出格式
第\(1\)到\(T\)行:从\(S\)到达城镇\(i\)的最小花费,如果不存在输出”NO PATH”。
样例输入
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
样例输入解释:一共六个城镇。在\(1-2,3-4,5-6\)之间有道路,花费分别是\(5,5,10\)。同时有三条航线:\(3\to 5, 4\to 6\)和\(1\to 3\),花费分别是\(-100,-100,-10\)。\(FJ\)的中心城镇在城镇\(4\)。
样例输出
NO PATH
NO PATH
5
0
-95
-100
样例输出解释:\(FJ\)的奶牛从\(4\)号城镇开始,可以通过道路到达\(3\)号城镇。然后他们会通过航线达到\(5\)和\(6\)号城镇。 但是不可能到达\(1\)和\(2\)号城镇。
分析
贴个链接,\(lc\)大佬已经解释的超级详细了:
详细分析
NO.4 数列运算
再贴个网站:
详细分析、
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+10;
const ll mod = 1000000007;
ll n;
ll a[maxn],f[maxn],sum[maxn],s[maxn];
ll pai[maxn];
ll quickpow(ll a,ll b){
ll ans = 1;
while(b){
if(b&1)ans = ans%mod*a%mod%mod;
a=a%mod*a%mod;
b>>=1;
}
return ans%mod;
}
int main(){
scanf("%lld",&n);
scanf("%lld",&a[1]);
pai[0]=1;
pai[1]=a[1];
for(int i=2;i<=n;++i){
scanf("%lld",&a[i]);
pai[i] = pai[i-1]%mod*a[i]%mod;
s[i] = s[i-1]%mod*a[i]%mod+a[i]%mod*quickpow(2,i-2)%mod;
}
f[1]=a[1];
sum[1]=f[1];
for(int i=1;i<=n;++i){
f[i] = sum[i-1]%mod+pai[i]%mod+s[i]%mod;
sum[i] = sum[i-1]%mod+f[i]%mod;
}
printf("%lld\n",f[n]%mod);
return 0;
}