<mark>2019组队赛第四场</mark> 解题报告
(2018湖南省第14届大学生计算机程序设计竞赛) 解题报告
by lj zx xzc
2019/4/2
前言:
xzc:
这次周赛大的不是很爽,我觉得有一部分原因是我们的状态不好,思维没有很活跃吧。 可能大家都没睡午觉吧。
这场比赛我们队出了3到题,一道签到,一道模拟?一道猜想,剩下的时间都在卡K题和B题。(B题zyw骗我们说他可以进化成dp手,我们一直在推传统的加减乘除的dp…)当时大家都没有往完全平方数的找规律的方向去想。
K题是因为没有memset所以wa,后来我也wa了几发,应该是前缀和的边界没处理好
J题是我的锅,当时lj跟我说它就是一颗树,可以做,但是我当时在卡B题,没有去开这道题,(当时我觉得zyf+zyw做了半天才做出来,我们短时间不一定能AC…),其实dfs还挺好写的
还有我们队一般是过了样例就交,有时候1A的非常爽,但是wa起来会罚时爆炸的
我觉得这些都是小问题,平时暴露出来也好,多注意,及时改掉就好啦,总比现场赛出锅要强。
我们队还是很厉害的!大家加油呀!
只要大家平时好好刷题,比赛头脑清醒,也可以像twt他们队一样nb的! 冲鸭!
vjudge链接: CCNUACM Team Contest 2019 Round #4
A.字符画
签到题
#include <bits/stdc++.h>
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define Mst(a,b) memset(a,(b),sizeof(a))
#define LL long long
#define MP make_pair
#define pb push_back
using namespace std;
const int maxn = 1e5+100;
char a[5][20] = {"oooooooooooo","..oo.o.o.o.o",
"oooo.o.o.ooo","o..o.o.o.o.o","oooooooooooo" };
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int w;
cin>>w;
for(int i=0;i<5;++i)
{
printf("%c%c%c",a[i][0],a[i][1],a[i][2]);
int cnt = 3;
for(int t=1;t<=3;t++)
{
for(int u=0;u<w;++u)
printf(".");
for(int p=0;p<3;p++)
printf("%c",a[i][cnt++]);
}
printf("\n");
}
return 0;
}
B.2018
题面:
Bobo 想统计满足下面条件的矩阵 A 的数量。
矩阵 A 有 n 行 m 列,每个元素都是正整数。第 i 行第 j 列的元素用 Aij 表示。
A1, 1 = 2018.
对于所有 2 ≤ i ≤ n, 1 ≤ j ≤ m,Ai, j 是 Ai − 1, j 的约数。
对于所有 1 ≤ i ≤ n, 2 ≤ j ≤ m,Ai, j 是 Ai, j − 1 的约数。
因为满足条件的矩阵 A 数量很多,Bobo 只想统计满足条件的矩阵数量除以 (109 + 7) 的余数。
1 ≤ n, m ≤ 2000
数据组数不超过 105.
Input
输入文件包含多组数据,请处理到文件结束。
每组数据包含 2 个整数 n 和 m.
Output
对于每组数据输出 1 个整数表示所求的数量除以 (1E9+7) 的余数。
Sample Input
1 1
1 2
2 2
2 3
2000 2000
Sample Output
1
4
25
81
570806941
Hint
对于第二组样例(n=1, m=2),满足条件的矩阵 A 有 (2018,2018),(2018,1009),(2018,2),(2018,1) 共 4 种。
解法:
- 观察得都是完全平方数,然后猜想规律
- ans[i][j] = dp[i][j]^2
- dp[i][j] = dp[i-1][j] + dp[i][j-1]+1
无语了。。。
代码:
#include <bits/stdc++.h>
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define LL long long
using namespace std;
const int maxn = 2005;
const int mod = 1e9+7;
LL dp[maxn][maxn];
int main()
{
For(i,1,2000) dp[1][i] = dp[i][1] = i;
For(i,2,2000)
For(j,2,2000)
dp[i][j] = (dp[i-1][j]+dp[i][j-1]+1)%mod;
int x,y;
while(scanf("%d%d",&x,&y)!=EOF)
{
LL ans = dp[x][y]*dp[x][y]%mod;
printf("%lld\n",ans);
}
return 0;
}
C.时间旅行
题面都懒得挂了,这题也是猜想题
zyf巨巨证明出来了
他过了,我们其它两个队也跟着都过了…
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
LL c,h;
while(scanf("%lld%lld",&h,&c)!=EOF)
{
LL ans = 0;
if(c<h) ans = h;
else ans = c+1;
printf("%lld\n",ans);
}
return 0;
}
D.卖萌表情
题面:
已知以下 4 种都是卖萌表情(空白的部分可以是任意字符。竖线是便于展示的分隔符,没有实际意义):
^ ^ | ^ | < | >
v | v v | > | <
| | < | >
给出 n 行 m 列的字符矩阵,Bobo 希望找出互不重叠的卖萌表情数量的最大值。互不重叠的意思是每个字符只属于至多一个卖萌表情。
1 ≤ n, m ≤ 1000
矩阵只包含 ^, v, <, > 4 种字符。
n × m 的和不超过 2 × 106.
Input
输入文件包含多组数据,请处理到文件结束。
每组数据的第一行包含 2 个整数 n 和 m.
接下来 n 行的第 i 行包含长度为 m 的字符串,表示字符矩阵的第 i 行。
Output
对于每组数据输出 1 个整数表示互不重叠的卖萌表情数量的最大值。
Sample Input
2 4
^^^^
>vv<
2 4
vvvv
>^^<
4 2
v>
<>
<>
^>
3 4
^>^>
v
>>>>
Sample Output
2
0
2
2
Hint
第一组样例中有 2 个第一种卖萌表情。
第四组样例中有 3 个卖萌表情,但是互不重叠的卖萌表情最多只有 2 个。
分析:
对每个矩阵的符号遍历,比对表情,比对成功,标记,计算得出结果
代码:
#include<cstdio>
char str[1005][1005];
int sign[1005][1005];
int main()
{
int n,m,count;
while(scanf("%d%d",&n,&m)!=EOF){
count = 0;
for(int i =0;i < n;i++){
scanf("%s",str[i]);
for(int j = 0;j < m;j++) sign[i][j] = 0;
}
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
if(sign[i][j]) continue;
if(str[i][j]=='^'){
if((i+1)<n&&(j-1)>=0&&(j+1)<m&&str[i+1][j-1]=='v'&&str[i+1][j+1]=='v'&&!sign[i+1][j-1]&&!sign[i+1][j+1]){
sign[i+1][j-1]=1;
sign[i+1][j+1]=1;
sign[i][j]=1;
count++;
}
else if((j+2)<m&&(i+1)<n&&str[i][j+2]=='^'&&str[i+1][j+1]=='v'&&!sign[i][j+2]&&!sign[i+1][j+1]){
sign[i][j+2] = 1;
sign[i+1][j+1] = 1;
sign[i][j] = 1;
count++;
}
}
else if(str[i][j]=='<'){
if((i+2)<n&&(j+1)<m&&str[i+1][j+1]=='>'&&str[i+2][j]=='<'&&!sign[i+1][j+1]&&!sign[i+2][j]){
sign[i][j]=1;
sign[i+1][j+1]=1;
sign[i+2][j] = 1;
count++;
}
}
else if(str[i][j]=='>'){
if((i+2)<n&&(j-1)>=0&&str[i+1][j-1]=='<'&&str[i+2][j]=='>'&&!sign[i+1][j-1]&&!sign[i+2][j]){
sign[i][j]=1;
sign[i+1][j-1] = 1;
sign[i+2][j] = 1;
count++;
}
}
}
}
printf("%d\n",count);
}
return 0;
}
E.千万别用树套树
题意:
维护一个区间的集合(相同元素可重复插入),有两种操作:
- 操作一(更新):1 L R
在集合中插入一个线段[L,R] - 操作二(询问):2 L R
询问集合中满足条件的元素[x,y]的个数,x <= L <= R <= y
(能完全盖住给定区间的线段的个数)
题目保证L,R,N<=100,000
数据保证询问的L,R 满足R - L <= 2,R<=n<=100,000
思路:
ansLeft = num(x>=L+1) + num(y<=R-1) + (R-L==2)*mp[L+1]
- 总的区间数 - 右端点小于R的区间 - 左端点大于L的区间 + 重复减去的就是答案。
- 因为R-L<=2。当R=L 或R = L+1时,不会重复
- 但R-1==L+1的时候,会重复减去左右区间均为L+1的区间数,我们用map维护左右端点相同的区间数即可
- 然后相当于两颗线段树维护所有元素左端点的区间和,和右端点的区间和即可
代码
#include <bits/stdc++.h>
#define Mst(a,b) memset(a,(b),sizeof(a))
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
using namespace std;
const int maxn = 1e5 + 100;
struct Node{
//int left,right;省略不写
int sum[2];
}tree[maxn<<2];
void build(int left,int right,int pos=1)
{
tree[pos].sum[0] = tree[pos].sum[1] = 0;
if(left==right) return;
int mid = (left+right)>>1;
build(left,mid,pos<<1);
build(mid+1,right,pos<<1|1);
}
//Y_Cl风格
int update1(int left,int right,int x,int id,int pos=1) //在区间tree[pos]:[left,right]区间内插入x
{
if(x>right||x<left) return tree[pos].sum[id];
if(left==right)
{
if(left==x) tree[pos].sum[id]++;
return tree[pos].sum[id];
}
int mid = (left+right)>>1;
int nL = update1(left,mid,x,id,pos<<1);
int nR = update1(mid+1,right,x,id,pos<<1|1);
return tree[pos].sum[id] = nL+nR;
}
//孙少风格,此处使用略快
void update(int left,int right,int x,int id,int pos=1)
{
if(left==right)
{
tree[pos].sum[id]++;
return;
}
int mid = (left+right)>>1;
if(x<=mid) update(left,mid,x,id,pos<<1);
else update(mid+1,right,x,id,pos<<1|1);
tree[pos].sum[id] = tree[pos<<1].sum[id]+tree[pos<<1|1].sum[id];
}
int query(int left,int right,int qx,int qy,int id,int pos=1)
{ if(qx>right||qy<left) return 0;
if(qx<=left&&right<=qy) return tree[pos].sum[id];
int mid = (left+right)>>1;
int nL = query(left,mid,qx,qy,id,pos<<1);
int nR = query(mid+1,right,qx,qy,id,pos<<1|1);
return nL+nR;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,q,L,R,t;
while(scanf("%d%d",&n,&q)!=EOF)
{
map<int,int> mp;
build(1,n);
int tot = 0;
while(q--)
{
scanf("%d%d%d",&t,&L,&R);
if(t==1)
{
update(1,n,L,0),update(1,n,R,1),++tot;
if(L==R) ++mp[L];
}
else
{
int small = query(1,n,1,R-1,1); //询问y<=R-1的区间的个数
int big = query(1,n,L+1,n,0); //询问x>=L+1的区间的个数
int add = (L==R-2)*mp[L+1];
int ans = tot - small - big + add;
printf("%d\n",ans);
}
}
}
return 0;
}
J.买一送一
ps:补了题发现我的提交是三个队里面运行时间最短的(416ms)没luan用
题面:
ICPCCamp 有 n 个商店,用 1, 2, …, n 编号。对于任意 i > 1,有从商店 pi 到 i 的单向道路。 同时,商店 i 出售类型为 ai 的商品。
Bobo 从商店 1 出发前往商店 i。他要在两个不同的商店购买商品(包括商店 1 和 i)。设他先购买的商品类型是 x,后购买的商品类型是 y,他用 fi 表示不同的有序对 ⟨x, y⟩ 的数量。 求出 f2, f3, …, fn 的值。
1 ≤ n ≤ 105
1 ≤ pi < i
1 ≤ ai ≤ n
n 的总和不超过 5 × 105.
Input
输入文件包含多组数据,请处理到文件结束。
每组数据的第一行包含 1 个整数 n.
第二行包含 (n − 1) 个整数 p2, p3, …, pn.
第三行包含 n 个整数 a1, a2, …, an.
Output
对于每组数据输出 (n − 1) 个整数表示 f2, f3, …, fn.
Sample Input
3
1 2
1 2 3
3
1 1
1 2 3
4
1 2 3
1 3 2 3
Sample Output
1
3
1
1
1
3
5
Hint
对于第三个样例,当 i = 4 时,可能的有序对 ⟨x, y⟩ 有 ⟨1, 2⟩,⟨1, 3⟩,⟨2, 3⟩,⟨3, 2⟩,⟨3, 3⟩ 共 5 种。所以 f4 = 5.
分析:
一共有n个节点,n-1条有向边构成的一个图。
- 它要么是一棵树,要么存在孤立的点和环,但是环的话,从起点1肯定是不可达的。所以我们从起点开始,dfs生成树就好了。
- 题目求从起点到每个点,图中在不同的两个商店买两个物品组成的有序对(x,y)的个数。 那答案可定就在这个点到根节点的一条唯一确定的路上。
- 可以从父节点递归过来,ans[now] = ans[fa[now]] + add
- add就是以当前节点(商店)为y,以前面节点为x,且和父节点的所有有序对不重复的有序对(x,y)的数量
- 那怎么保证不重复呢?
- 如果这个点在这条dfs路径中是第一次出现,那么以它为y,肯定不会重复,那么add就等于前面所有商店出现的不同货物的种类。所以我们要维护一个numType数组表示从根节点到每个节点图中不同商品的种类数
如果这个点的货物种类t在从根节点到它的这条dfs路径中已经出现过了,那么我们需要知道t最后出现的位置lastSeen。这样的话lastSeen之前的x都和t配过(x,t)这样的有序对了,lastSeen之后的,当前节点之前的货物种类就是add - 还有一个就是要考虑(t,t)正样x和y相同的有序对,如果之前出现t的次数>=2,那么(t,t)就已经出现过了,不让的话add++就好了
- 我是和zyf一样用vector v[maxn]来按顺序存每种商品出现的位置(节点编号),往下递归时push_back(), 回溯时pop_back() 我看twt是用一个set维护的
代码:
/* Accepted 416ms 13276kB 1739 C++ 2019-04-01 19:49:39 */
#include <bits/stdc++.h>
#define Mst(a,b) memset(a,(b),sizeof(a))
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define LL long long
#define MP make_pair
#define pb push_back
using namespace std;
const int maxn = 1e5 + 20;
int head[maxn],tot;
struct Edge{ ///邻接表存边
int to,Next;
}edge[maxn];
void initG(int n)
{
memset(head,-1,sizeof(int)*(n+2));
tot = 0;
}
void addedge(int u,int v)
{
edge[tot].to = v;
edge[tot].Next = head[u];
head[u] = tot++;
}
vector<int> pos[maxn+2]; ///按照dfs的顺序存储每种商品对应的商店号
int numType[maxn]; //存每个节点到根节点这条路中不同货物的数量
LL ans[maxn]; //存储节点对应的答案
int a[maxn]; //type of goods 存商店所卖货物的种类
void dfs(int fa,int now)
{
int lenth = 0;
if(now==1) //如果是根节点,直接往下dfs,ans和numType已经初始化
goto Line;
numType[now] = numType[fa];
if(!pos[a[now]].size()) ///if this type of good haven't appeared, ans++
{
numType[now] += 1; ///如果前面没有出现过这种货物,对应的货物种类等于父节点+1
}
lenth = pos[a[now]].size(); ///前面出现的同种货物的次数
if(!lenth) //this type hasn't appeared before
{ //前面没有出现过这种货物
ans[now] = ans[fa] + numType[fa];
}
else
{
int lastSeen = pos[a[now]][lenth-1];//the last pos(node) that this type appeared
ans[now] = ans[fa]+numType[fa]-numType[lastSeen];
if(lenth==1) ans[now]++; // 3 3
}
Line:
pos[a[now]].push_back(now);
for(int i=head[now];i!=-1;i=edge[i].Next)
{
int to = edge[i].to;
dfs(now,to);
}
pos[a[now]].pop_back();
}
int main()
{
int n,p;
while(scanf("%d",&n)!=EOF)
{
initG(n);
memset(ans,0,sizeof(int)*(n+2));
For(i,2,n)
scanf("%d",&p),addedge(p,i);
For(i,1,n)
scanf("%d",a+i);
ans[1] = 0;
numType[1] = 1;
dfs(1,1);
For(i,2,n)
printf("%lld\n",ans[i]);
}
return 0;
}
K.Use FFT
题意:
A = a0 + a1*x^1 + a2 * x^2 + … + am * x^m
B = b0 + b1*x^1 + b2 * x^2 + … + bn * x^n
C = A * B
= c0 + c1*x^1 + c2 * x^2 + … + c[m+n] *x^(m+n)
多组询问,每组询问给出L,R(0<=L,R<=m+n)
求c[L] + c[L+1] + … +c[R-1]+c[R]
分析:
就是a[i] * sumb[j] 求和,a from … to …
见下面的例子:
m = 2, n = 3
C[0] = a[0] * b[0]
C[1] = a[0] * b[1] + a[1] * b[0]
C[2] = a[0] * b[2] + a[1] * b[1] + a[2] * b[0]
C[3] = a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0]
C[4] = a[0] * b[4] + a[1] * b[3] + a[2] * b[2] + a[3] * b[1] + a[4] * b[0]
C[5] = a[0] * b[5] + a[1] * b[4] + a[2] * b[3] + a[3] * b[2] + a[4] * b[1] + a[5] * b[0]
C[0] = a[0] * b[0]
C[1] = a[0] * b[1] + a[1] * b[0]
C[2] = a[0] * b[2] + a[1] * b[1] + a[2] * b[0]
C[3] = a[0] * b[3] + a[1] * b[2] + a[2] * b[1]
C[4] = a[1] * b[3] + a[2] * b[2]
C[5] = a[2] * b[3]
m = 3, n = 4
C[0] = a[0] * b[0]
C[1] = a[0] * b[1] + a[1] * b[0]
C[2] = a[0] * b[2] + a[1] * b[1] + a[2] * b[0]
C[3] = a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0]
C[4] = a[0] * b[4] + a[1] * b[3] + a[2] * b[2] + a[3] * b[1] + a[4] * b[0]
C[5] = a[0] * b[5] + a[1] * b[4] + a[2] * b[3] + a[3] * b[2] + a[4] * b[1] + a[5] * b[0]
C[6] = a[0] * b[6] + a[1] * b[5] + a[2] * b[4] + a[3] * b[3] + a[4] * b[2] + a[5] * b[1] + a[6] * b[0]
C[7] = a[0] * b[7] + a[1] * b[6] + a[2] * b[5] + a[3] * b[4] + a[4] * b[3] + a[5] * b[2] + a[6] * b[1] + a[7] * b[0]
C[0] = a[0] * b[0]
C[1] = a[0] * b[1] + a[1] * b[0]
C[2] = a[0] * b[2] + a[1] * b[1] + a[2] * b[0]
C[3] = a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0]
C[4] = a[0] * b[4] + a[1] * b[3] + a[2] * b[2] + a[3] * b[1]
C[5] = a[1] * b[4] + a[2] * b[3] + a[3] * b[2]
C[6] = a[2] * b[4] + a[3] * b[3]
C[7] = a[3] * b[4]
是不是一目了然?^_^
代码:
//by xzc
#include <bits/stdc++.h>
#define Mst(a,b) memset(a,(b),sizeof(a))
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define LL long long
#define MP make_pair
#define pb push_back
using namespace std;
const int maxn = 5e5 + 10;
const LL mod = 1e9+7;
LL a[maxn],b[maxn],sumb[maxn];
int m,n;
LL gb(int i)
{
if(i<0) return 0;
if(i>n) return sumb[n];
return sumb[i];
}
LL S(int left,int right)
{
return (gb(right)-gb(left-1)+mod)%mod;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int L,R;
while(scanf("%d%d%d%d",&m,&n,&L,&R)!=EOF)
{
For(i,0,m) scanf("%lld",a+i);
For(i,0,n) scanf("%lld",b+i);
sumb[0] = b[0];
For(i,1,n) sumb[i] = (sumb[i-1]+b[i])%mod;
LL ans = 0;
int right = min(R,m);
For(i,0,right)
ans = (ans+a[i]*S(max(0,L-i),R-i)%mod) % mod;
printf("%lld\n",ans);
}
return 0;
}
//by lj
#include<cstdio>
#include<iostream>
using namespace std;
const int mod = 1e9+7;
const int N = 5e5+10;
long long a[N],b[N];
long long sum[N];
int main(){
int n,m,l,r,mini,maxe;
long long ans;
while(scanf("%d%d%d%d",&n,&m,&l,&r)!=EOF){
ans = 0;
for(int i = 0;i <= n;i++) scanf("%lld",&a[i]);
for(int i = 0;i <= m;i++){
scanf("%d",&b[i]);
if(i==0) sum[i] = b[i];
else sum[i] = sum[i-1]+b[i];
sum[i] = sum[i]%mod;
}
for(int i = m+1;i<=m+n;i++) sum[i] = sum[i-1];
for(int i = 0;i <= r;i++){
if(i>n) break;
if(l-i-1<0) ans += sum[r-i]*a[i]%mod;
else ans+=(sum[r-i]-sum[l-i-1]+mod)%mod*a[i]%mod;
ans = ans%mod;
}
printf("%lld\n",ans);
}
}