0%

7月28日 Codeforces Round #498 题目链接:http://codeforces.com/contest/1006

A - Adjacent Replacements & B - Polycarp’s Practice

比较水呢。。不说

C- Three Parts of the Array

思路: 也很水呢。。。就是一个双指针,记录两边的和就好了。其余的看代码。。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int n, l, r;
LL lsum, rsum, a[1000005], ans;

int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%lld", &a[i]);
l=1, r=n;
while(l<=r)
{
if(lsum==rsum) ans=lsum;
if(lsum<=rsum) lsum+=a[l], l++;
else rsum+=a[r], r--;
}
if(lsum==rsum) ans=lsum;
printf("%lld\n", ans);
}

D - Two Strings Swaps

思路: 毒瘤,卡了超级久。。。首先思路很简单,因为$a_i$, $a_{n+1-i}$, $b_i$, $b_{n+1-i}$直间能够任意交换,所以我们可以把这4个元素看成一块,计算出要改几个字符。

关于答案的的计算方法很毒瘤,就是分类讨论!!有很多情况,也有很多细节,在讨论过程中思路一定要清晰,千万不要漏情况了。。我就漏了QWQ

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int n, ans;
char a[100005], b[100005];
map<char, int> mp;

int main()
{
scanf("%d", &n);
scanf("%s", a+1);
scanf("%s", b+1);
for(int i=1; i<=(n+1)/2; i++)
{
if(i+i==n+1)
{
if(a[i]!=b[i]) ans++;
continue;
}
mp.clear();
mp[a[i]]++, mp[a[n+1-i]]++;
mp[b[i]]++, mp[b[n+1-i]]++;
if(mp.size()==2 && mp[a[i]]!=2) ans++;
if(mp.size()==3)
{
if(a[i]==a[n+1-i]) ans+=2;
else ans++;
}
if(mp.size()==4) ans+=2;
}
printf("%d\n", ans);
}

E - Military Problem

思路: 首先一棵子树在DFS序上是连续的,我们可以利用这个性质,然后这道题就是水题了。我们只要一遍DFS处理处子树大小和整棵树的DFS序,如果$k$大于了$x$的子树大小,那么答案就是-1,否则就是DFS序在$x$后$k-1$个对应的节点。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <bits/stdc++.h>
#define MAXN 200005
using namespace std;

int n, q, cnt, id, siz[MAXN],ver[MAXN], dfn[MAXN], head[MAXN];

vector<int> g[MAXN];

void dfs(int x, int fa)
{
siz[x]=1;
dfn[x]=++id; ver[id]=x;
for(int i=0; i<g[x].size(); i++)
{
int to=g[x][i];
if(to==fa) continue;
dfs(to, x);
siz[x]+=siz[to];
}
}

int main()
{
scanf("%d%d", &n, &q);
for(int i=2; i<=n; i++)
{
int temp;
scanf("%d", &temp);
g[i].push_back(temp);
g[temp].push_back(i);
}
dfs(1, 0);
for(int i=1; i<=q; i++)
{
int x, k;
scanf("%d%d", &x, &k);
if(k>siz[x]) printf("-1\n");
else printf("%d\n", ver[dfn[x]+k-1]);
}
}

F - Xor-Paths

思路: 看范围就知道是双向爆搜。。。然后就直接码就好了,然后就是中间相交处理答案时有一些细节要注意。中间的那一条是计算过两次的,所以在更新$cnt$数组是还要异或一下那个数字。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int n, m;
LL k, ans, a[35][35];
map<LL, LL> cnt[35][35];

void dfs1(int x, int y, LL num)
{

if(x+y==(n+m)/2+1) {cnt[x][y][num^a[x][y]]++; return;}
if(x<n) dfs1(x+1, y, num^a[x+1][y]);
if(y<m) dfs1(x, y+1, num^a[x][y+1]);
}

void dfs2(int x, int y, LL num)
{

if(x+y==(n+m)/2+1) {ans+=cnt[x][y][num^k]; return;}
if(x>1) dfs2(x-1, y, num^a[x-1][y]);
if(y>1) dfs2(x, y-1, num^a[x][y-1]);
}

int main()
{
scanf("%d%d%lld", &n, &m, &k);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) scanf("%lld", &a[i][j]);
dfs1(1, 1, a[1][1]);
dfs2(n, m, a[n][m]);
printf("%lld\n", ans);
}

7月26日 Educational Codeforces Round 47 题目链接:http://codeforces.com/contest/1009

A - Game Shopping & B - Minimum Ternary String

简单,不讲。。。

C - Annoying Present

思路: 其实也比较水。。。首先参数$x$对答案的贡献是不会变的,根据一些小学数学知识可以知道:当$d$为正时,选取的位置越靠边贡献越大;当$d$为负时,选取的位置越靠中间贡献越大。因此分类讨论一下就好了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <bits/stdc++.h>
#define LL long long
using namespace std;

LL ans, n, m, x, d;;

int main()
{
scanf("%lld%lld", &n, &m);
for(int i=1; i<=m; i++)
{
scanf("%lld%lld", &x, &d);
ans+=n*x;
if(d>0) ans+=(n-1)*n/2*d;
else
{
if(n&1) ans+=(n/2)*(n/2+1)*d;
else ans+=n*n/4*d;
}
}
printf("%lf\n", (double)ans/n);
}

D - Relatively Prime Graph

思路: 虽然是构造一张图,其实和图论没有什么关系。。。

看懂题意以后可以发现你就是要在$1-n$内找出$m$对互素的数,于是可以暴力枚举,知道枚举出$m$对数。因为这是实在是太暴力了,比赛时还在怀疑算法不够优秀。其实互质数对的分布是玄学的,所以一般是不好卡你暴力的,再加上CF的神机,放心的打就好了。(实测46ms) 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int n, m, cnt;
pair<int, int> ans[100005];

int gcd(int x, int y) {return y==0 ? x : gcd(y, x%y);}

void solve()
{
for(int i=1; i<=n; i++)
{
for(int j=i+1; j<=n; j++)
if(gcd(i, j)==1)
{
ans[++cnt].first=i; ans[cnt].second=j;
if(cnt==m) return;
}
}
}

int main()
{
scanf("%d%d", &n, &m);
if(m<n-1)
{
printf("Impossible\n");
return 0;
}
solve();
if(cnt<m) printf("Impossible\n");
else
{
printf("Possible\n");
for(int i=1; i<=m; i++) printf("%d %d\n", ans[i].first, ans[i].second);
}
}

E - Intercity Travelling

思路: 一看题目,哇,和JXOI的风格好像的QVQ,都是小清新的期望计数题。虽然是期望,不过同样是套了期望的说法的一些计数类问题。

首先想到$n^2$的暴力DP,先处理前缀和。设$g[i]$为有多少种方法到达$i$点,$f$就是答案所求的东西,那么DP的方法就如下面代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
g[0]=1;
for(int i=1; i<=n; i++)
{
scanf("%lld", &a[i]);
a[i]=(a[i]+a[i-1])%p;
}
for(int i=1; i<=n; i++)
for(int j=0; j<i; j++)
{
f[i]=(f[i]+f[j]+a[i-j]*g[j])%p;
g[i]=(g[i]+g[j])%p;
}

然后我们打表发现$g[i]$就是$2^{i-1}$,又发现$f[i]$只由$a$中的元素组成,且组成方法与$a$中元素的值无关。于是再次打表,打出$f[i]$由$a$中的元素怎么构成,于是发现其中$h[n+1-i]$为构成$f[n]$(即答案)中$a[i]$的系数。即: 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define LL long long
#define MAXN 1000006
#define p 998244353
using namespace std;

LL n, ans, h[MAXN], g[MAXN], a[MAXN], sum[MAXN];

int main()
{
scanf("%lld", &n);
g[1]=g[0]=1;
for(int i=2; i<=n; i++) g[i]=(g[i-1]*2)%p;
for(int i=1; i<=n; i++) scanf("%lld", &a[i]), a[i]=(a[i]+a[i-1])%p;

for(int i=1; i<=n; i++)
{
h[i]=(sum[i-1]+g[i-1])%p;
sum[i]=(sum[i-1]+h[i])%p;
}
for(int i=1; i<=n; i++) ans=(a[i]*h[n-i+1]+ans)%p;
printf("%lld\n", ans%p);
}

F - Dominant Indices

思路: 一直没有学树上启发式合并。膜题解发现要用这种操作,于是学了一下。个人理解就是遍历每个节点,然后从每个节点再开始另一个搜索,处理出该节点子树的信息,这样显然最坏是$O(N^2)$的。然而我们可以从先遍历节点,回溯时保留父亲节点重儿子的信息,这样从父亲节点开始的另一个搜索就可以不用搜重儿子。于是我们保证了时间复杂度在$O(logn)$。

这道题也是类似,要求子树重节点最多的那一层,于是就按照启发式合并去做就好了。 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <bits/stdc++.h>
#define MAXN 1000006
using namespace std;

int n, cnt, maxnum, maxans, siz[MAXN], head[MAXN], dep[MAXN], ans[MAXN], num[MAXN], tag[MAXN];

struct Edge {int next, to;}edge[MAXN*2];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}

void dfs1(int x, int fa)
{
siz[x]=1;
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
dep[to]=dep[x]+1;
dfs1(to, x);
siz[x]+=siz[to];
}
}

void update(int x)
{
if(num[dep[x]]>maxnum) maxnum=num[dep[x]], maxans=dep[x];
if(num[dep[x]]==maxnum && dep[x]<maxans) maxans=dep[x];
}

void add(int x, int fa, int op)
{
num[dep[x]]+=op;
update(x);
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
add(to, x, op);
}
}

void dfs2(int x, int fa, int keep)
{
int maxson=-1, maxsize=0;
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
if(siz[to]>maxsize) maxson=to, maxsize=siz[to];
}

for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa || to==maxson) continue;
dfs2(to, x, 0);
}
if(maxson!=-1) dfs2(maxson, x, 1);

num[dep[x]]++;
update(x);
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa || to==maxson) continue;
add(to, x, 1);
}
ans[x]=maxans;

if(keep==0)
{
num[dep[x]]--;
update(x);
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
add(to, x, -1);
}
maxnum=maxans=0;
}
}

int main()
{
scanf("%d", &n);
for(int i=1; i<n; i++)
{
int x, y;
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
dfs1(1, 0);
dfs2(1, 0, 1);
for(int i=1; i<=n; i++) printf("%d\n", ans[i]-dep[i]);
}

问题描述:

有一个球形空间产生器能够在$n$维空间中产生一个坚硬的球体。现在,你被困在了这个$n$维球体中,你只知道球面上$n+1$个点的坐标,你需要以最快的速度确定这个$n$维球体的球心坐标,以便于摧毁这个球形空间产生器。

输入:

第一行是一个整数$n(1<=N=10)$。接下来的$n+1$行,每行有$n$个实数,表示球面上一点的$n$维坐标。每一个实数精确到小数点后6位,且其绝对值都不超过20000。

输出:

有且只有一行,依次给出球心的$n$维坐标,两个实数之间用一个空格隔开。每个实数精确到小数点后3位。数据保证有解。

思路:

这应该是比较裸的高斯消元了。球心到$n+1$个点距离相等,那么我们把距离当作常量就有$n+1$个方程。把常量减去后就剩下$n$个方程$n$个未知数,这时我们就可以用高斯消元了。

方程我就不列出来了(懒),然后因为方程保证有解,所以代码还是比较容易打的。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#define MAXN 20
#define eps 1e-8
using namespace std;

int n;
double a[MAXN][MAXN], b[MAXN], c[MAXN][MAXN];

int main()
{
scanf("%d", &n);
for(int i=1; i<=n+1; i++)
for(int j=1; j<=n; j++) scanf("%lf", &a[i][j]);
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
c[i][j]=2*(a[i][j]-a[i+1][j]);
b[i]+=a[i][j]*a[i][j]-a[i+1][j]*a[i+1][j];
}

for(int i=1; i<=n; i++)
{
for(int j=i; j<=n; j++)
if(fabs(c[j][i])>eps)
{
for(int k=1; k<=n; k++) swap(c[i][k], c[j][k]);
swap(b[i], b[j]);
}
for(int j=1; j<=n; j++)
if(i!=j)
{
double rate=c[j][i]/c[i][i];
for(int k=i; k<=n; k++) c[j][k]-=c[i][k]*rate;
b[j]-=b[i]*rate;
}
}
for(int i=1; i<n; i++) printf("%.3lf ", b[i]/c[i][i]);
printf("%.3lf\n", b[n]/c[n][n]);
}

问题描述:

石头游戏在一个n行m列的方格阵上进行。每个格子对应了一个编号在0~9之间的操作序列。操作序列是一个长度不超过6且循环执行、每秒执行一个字符的字符串。它包括: 1. 数字0~9:拿0~9个石头到该格子。 2. NWSE:把这个格子内所有的石头推到相邻的格子。 3. D:拿走这个格子的石头。

石头游戏的问题是:当这个石头游戏进行了t秒之后,所有方格中最多的格子有多少个石头。

输入:

第一行三个整数n, m, t, act。 接下来n行,每行m个字符,表示每个格子对应的操作序列。 最后act行,每行一个字符串,表示从0开始的每个操作序列。

输出:

一个整数:游戏进行了t秒之后,所有方格中最多的格子有多少个石头。

思路:

这种矩阵加速递推的题重点是建模和构造矩阵。好有趣啊。。

我们可以将方格阵的两位压到一维上,那么状态矩阵$f$就是一个$1*nm$的矩阵。因为操作序列的长度不超过6,所以操作最多60次一个循环,因此我们可以对于每一次设计一个转移矩阵。然后转移矩阵$a$的构造方法是: 1. 如果位置$i,j$的操作为”WENS”,那么我们就令$a[pos(i,j)][pos(i’,j’)]=1$。其中$i’,j’$为操作的对应位置。 2. 如果位置$i,j$的操作为数字$x$,那么我们就令$a[0][pos(i,j)]=x$。 3. 另外我们要保证$a[0][0]=1$

这样我们就是把状态矩阵$f[0]$当成了石头来源,$a[0][0]=1$保证了$f[0]=1$。转移矩阵构造好以后就利用矩阵快速幂优化一下递推,这个就不多说了。主要还是构造转移矩阵,就像网络流主要是建图一样。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 100
#define LL long long
#define pos(i, j) (i-1)*m+j
using namespace std;

int n, m, t, act, len[MAXN], op[MAXN][MAXN];
LL maxn, ans[MAXN][MAXN], a[MAXN][MAXN][MAXN], b[MAXN][MAXN];
char opt[MAXN][MAXN];

void init()
{
ans[0][0]=1;
for(int i=0; i<=pos(n, m); i++) b[i][i]=1;
for(int i=0; i<60; i++) a[i][0][0]=1;
}

void mul(LL x[MAXN][MAXN], LL y[MAXN][MAXN])
{
LL c[MAXN][MAXN];
memset(c, 0, sizeof(c));
for(int i=0; i<=n*m; i++)
for(int j=0; j<=n*m; j++)
for(int k=0; k<=n*m; k++) c[i][j]+=x[i][k]*y[k][j];
memcpy(x, c, sizeof(c));
}

void ksm(int y)
{
while(y)
{
if(y&1) mul(ans, b);
mul(b, b); y>>=1;
}
}

int main()
{
scanf("%d%d%d%d", &n, &m, &t, &act);
init();
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) scanf("%1d", &op[i][j]);
for(int i=0; i<act; i++)
{
scanf("%s", opt[i]);
len[i]=strlen(opt[i]);
}
for(int k=0; k<60; k++)
{
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
char act=opt[op[i][j]][k%len[op[i][j]]];
if(act=='E' && j<m) a[k][pos(i, j)][pos(i, j+1)]=1;
if(act=='N' && i>1) a[k][pos(i, j)][pos(i-1, j)]=1;
if(act=='W' && j>1) a[k][pos(i, j)][pos(i, j-1)]=1;
if(act=='S' && i<n) a[k][pos(i, j)][pos(i+1, j)]=1;
if(act>='0' && act<='9')
{
a[k][pos(i, j)][pos(i, j)]=1;
a[k][0][pos(i, j)]=act-'0';
}
}
mul(b, a[k]);
}
ksm(t/60);
for(int i=0; i<t%60; i++) mul(ans, a[i]);
for(int i=1; i<=n*m; i++) maxn=max(maxn, ans[0][i]);
printf("%lld\n", maxn);
}

问题描述:

你需要求出$A^B$的所有约数之和。

输入:

只有两个数A,B $(1<=A,B<=5*10^7)$

输出:

一个数,约数之和($mod 9901$)

思路:

一开始不知道算术基本定理时看得比较懵逼。。。后面才发现是傻逼题。。

首先任何大于1的正整数$N$都可以分解成$ N =p_1^{c_1}p_2^{c_2}p_3^{c_3}···p_m^{c_m} $的形式,其中$c_i$为正整数,$p_1<p_2<p_3<···<p_m$且$p_i$均为质数。根据这个我们可以有个推论就是$N$的正约数之和为$ \prod_{i=1}^m (\sum_{j=0}^{c_i} p_i ^j) $。

这样就很容易得得出这题的答案:$ ans= \prod_{i=1}^m (\sum_{j=0}^{c_i*B} p_i ^j) $。这个就可以用等比数列的求和公式来求,要注意的就是除要变成乘逆元,当逆元不存在时要特判。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#define MAXN 1000005
#define mod 9901
#define LL long long
using namespace std;

LL a, b, c[MAXN][2], ans=1, cnt;

void div(LL num)
{
for(int i=2; i*i<=num; i++)
if(num%i==0)
{
c[++cnt][0]=i;
while(num%i==0) c[cnt][1]++, num/=i;
}
if(num!=1) c[++cnt][0]=num, c[cnt][1]=1;
}

LL ksm(LL x, LL y)
{
LL ans=1;
while(y)
{
if(y&1) ans=(x*ans)%mod;
x=(x*x)%mod; y>>=1;
}
return ans;
}

LL sum(LL x, LL y)
{
if(y==0) return 1;
if(y&1) return (1+ksm(x, y/2+1))*sum(x, y/2)%mod;
else return ((1+ksm(x, y/2+1))*sum(x, y/2-1)+ksm(x, y/2))%mod;
}

int main()
{
while(scanf("%lld%lld", &a, &b)!=EOF)
{
div(a);
for(int i=1; i<=cnt; i++) ans=sum(c[i][0], c[i][1]*b)*ans%mod;
printf("%lld\n", ans);
}
}