2019组队赛第四场(2018湖南省第14届大学生计算机程序设计竞赛)解题报告 Apare_xzc

2019组队赛第四场 解题报告

(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. 操作一(更新):1 L R
       在集合中插入一个线段[L,R]
  2. 操作二(询问):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);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40531479/article/details/88982964
今日推荐