0%

6月11日 AtCoder Beginner Contest 099 题目链接:https://abc099.contest.atcoder.jp/

A - ABD & B - Stone Monument

过水,不多说

C - Strange Bank

思路: 一开始分析了下复杂度,以为迭代加深DFS可做。结果大样例跑了一万年QWQ。。

后面就想到了背包,完全背包直接从前向后扫一遍,就OK了。

代码:

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
#include <bits/stdc++.h>
#define LL long long
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;

int n, ans, f[MAXN];
bool flag;
int n6[100], n9[100];


int main()
{
memset(f, 0x3f, sizeof(f));
scanf("%d", &n);
for(int i=0; i<=7; i++) n6[i]=n9[i]=1;
for(int i=0; i<=7; i++)
{
for(int j=1; j<=i; j++) n6[i]*=6;
for(int j=1; j<=i; j++) n9[i]*=9;
}
f[0]=0;
for(int i=0; i<=n; i++)
{
for(int j=0; j<=7; j++)
{
if(i+n6[j]<=n) f[i+n6[j]]=min(f[i+n6[j]], f[i]+1);
if(i+n9[j]<=n) f[i+n9[j]]=min(f[i+n9[j]], f[i]+1);
}
}
printf("%d\n", f[n]);
}

D - Good Grid

思路: 开始想到暴力$n^2*c^3$,后面发现可以通过预处理将复杂度降为$n^2*c+c^3$,然后就可以过了。

代码:

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
#define MAXN 300005
#define INF 2147483647
using namespace std;

int n, k, ans=INF, num[35][5];
int mp[505][505], c[35][35];

int main()
{
scanf("%d%d", &n, &k);
for(int i=1; i<=k; i++) for(int j=1; j<=k; j++) scanf("%d", &c[i][j]);
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) scanf("%d", &mp[i][j]);
for(int x=1; x<=k; x++)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++) num[x][(i+j)%3]+=c[mp[i][j]][x];
}
}
for(int i=1; i<=k; i++)
{
for(int j=1; j<=k; j++)
{
for(int l=1; l<=k; l++)
{
if(i!=j && j!=l && l!=i) ans=min(ans, num[i][0]+num[j][1]+num[l][2]);
}
}
}
printf("%d\n", ans);
}

6月11日 Educational Codeforces Round 45 题目链接:http://codeforces.com/contest/990

A - Commentary Boxes & B - Micro-World

过水,不多说。

C - Bracket Sequences Concatenation Problem

思路: 类似于模拟吧,不过思路一定要清晰(我弄错了很多次QWQ)。

可以用一个map记录有多少序列有x个左(右)括号未匹配,然后答案就可以通过这个计算得到。

代码:

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 LL long long
#define MAXN 300005
#define INF 0x3f3f3f3f
using namespace std;

int n, l, r;
LL ans;
map<LL, LL> mp;
char s[MAXN];

int main()
{
//freopen("a.in", "r", stdin);
//freopen("a1.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%s", s);
int len=strlen(s), l=0, r=0;
for(int j=0; j<len; j++)
{
if(r==0 && s[j]==')') l--;
if(r!=0 && s[j]==')') r--;
if(l==0 && s[j]=='(') r++;
if(l!=0 && s[j]=='(') r++;
}
if(l==0 && r==0) mp[0]++;
else
{
if(l==0) mp[r]++;
if(r==0) mp[l]++;
}
}
for(int i=0; i<=MAXN; i++)
{
ans+=mp[i]*mp[-i];
}
cout<<ans;
}

D - Graph And Its Complement

思路: 是一道结论题吧。可以考虑当n>3时如果a,b均不等于1,则该情况不存在。当n=1或n=2并且a=b=1时同样无解。

其余情况都是有解的,然后就考虑输出方案。这时a或b有一个为1,如果b为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
#include<bits/stdc++.h>
using namespace std;

int n, a, b, ans[1005][1005];
bool flag;

int main()
{
scanf("%d%d%d", &n, &a, &b);
if(a<b) swap(a, b), flag=1;
if(b!=1 || ((n==2 || n==3) && (a==1 && b==1))) {printf("NO\n"); return 0;}
printf("YES\n");
for(int i=1; i<=n-a; i++)
{
ans[i][i+1]=1;
ans[i+1][i]=1;
}
if(!flag)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++) printf("%d", ans[i][j]);
printf("\n");
}
}
else
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j) printf("0");
else printf("%d", 1^ans[i][j]);
}
printf("\n");
}
}

}

E - Post Lamps

思路: 也是一个比较简单的模拟+贪心。每一次放灯时尽量放右边,并且灯之间全要覆盖。要判一下能否符合要求,如果整个序列都符合要求,就更新一下答案。

代码:

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
#include <bits/stdc++.h>
#define INF 1e18
#define LL long long
#define lld I64d
#define MAXN 1000005
using namespace std;

LL n, m, k, temp, ans=INF, lef[MAXN], a[MAXN];
bool vis[MAXN];

LL solve(LL x)
{
LL r=0, cnt=0, pos=-1;
while(r<n)
{
if(pos>=lef[r]) return INF;
pos=lef[r];
r=pos+x;
cnt++;
}
return cnt;
}

int main()
{
scanf("%lld%lld%lld", &n, &m, &k);
for(int i=1; i<=m; i++)
{
scanf("%lld", &temp);
vis[temp]=true;
}
if(vis[0]) lef[0]=-1;
for(int i=1; i<n; i++)
{
if(vis[i]) lef[i]=lef[i-1];
else lef[i]=i;
}
for(int i=1; i<=k; i++) scanf("%lld", &a[i]);
for(int i=1; i<=k; i++)
{
temp=solve(i);
if(temp!=INF && ans>temp*a[i]) ans=temp*a[i];
}
if(ans==INF) printf("-1\n");
else printf("%lld\n", ans);
}

问题描述

大意就是:给你一个长度为n(n<=10000)的序列,再給你m(m<=20000)个询问,问你在给定区间[l,r]中的众数。(题目强制要求在线)

输入

第一行n, m 第二行给你序列(序列元素小于10^9) 接下来m行每行两个数l, r。

输出

m行,每行输出第m个询问的答案。

思路

由于众数是不方便进行区间合并的,因此不能够使用线段树或树状数组等数据结构。在这种情况下,分块是一种可能的选择。

分块时,我们考虑一个区间的众数只可能是:包含在区间中所有完整块的众数或者是包含在不完整的块中的数。这样,对于任意一个区间我们把范围缩小到了$2*size$($size$为块的大小)个数。

我们可以预处理出第i个完整块到第j个完整块中的众数,储存为f[i][j]。然后我们就考虑如何计算这些数在区间中的出现次数。我们可以用vector储存下标,然后用二分查找找出区间内的元素个数,这个即是该数在区间内的出现次数。

分析一下复杂度,设一个完整块的大小为size。处理询问的复杂度是$m*size*log(n)$,预处理的复杂度是$n*n/size$,这样size在大约200差不多就不会TLE了。

代码

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
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <vector>
#define MAXN 40005
using namespace std;

int n, m, size, num, id, a[MAXN], pos[MAXN], L[MAXN], R[MAXN], ans;
int cnt[MAXN], val[MAXN], f[500][500];

map<int, int> mmp;
vector<int> vec[MAXN];

void init()
{
size=sqrt(n), num=n/size;
for(int i=1; i<=num; i++) L[i]=(i-1)*size+1, R[i]=i*size;
if(R[num]<n) {L[++num]=R[num-1]+1; R[num]=n;}
for(int i=1; i<=num; i++) for(int j=L[i]; j<=R[i]; j++) pos[j]=i;

for(int i=1; i<=num; i++)
{
int maxn=0, ans=0;
memset(cnt, 0, sizeof(cnt));
for(int j=L[i]; j<=n; j++)
{
cnt[a[j]]++;
int p=pos[j];
if(cnt[a[j]]>maxn || (cnt[a[j]]==maxn && val[a[j]]<val[ans])) ans=a[j], maxn=cnt[a[j]];
f[i][p]=ans;
}
}
}

int cal(int l, int r, int x)
{
return upper_bound(vec[x].begin(), vec[x].end(), r)-lower_bound(vec[x].begin(), vec[x].end(), l);
}

int query(int l, int r)
{
int ans=0, maxn=0, x=pos[l], y=pos[r];
if(x==y)
{
for(int i=l; i<=r; i++)
{
int t=cal(l, r, a[i]);
if(t>maxn || (t==maxn && val[a[i]]<val[ans])) ans=a[i], maxn=t;
}
}
else
{
ans=f[x+1][y-1];
maxn=cal(l, r, ans);
for(int i=l; i<=R[x]; i++)
{
int t=cal(l, r, a[i]);
if(t>maxn || (t==maxn && val[a[i]]<val[ans])) ans=a[i], maxn=t;
}
for(int i=L[y]; i<=r; i++)
{
int t=cal(l, r, a[i]);
if(t>maxn || (t==maxn && val[a[i]]<val[ans])) ans=a[i], maxn=t;
}
}
return ans;
}

int main()
{
//freopen("a.in", "r", stdin);
//freopen("a2.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
{
scanf("%d", &a[i]);
if(!mmp[a[i]])
{
mmp[a[i]]= ++id;
val[id]=a[i];
}
a[i]=mmp[a[i]];
vec[a[i]].push_back(i);
}
init();
for(int i=1; i<=m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
l=(l+ans-1)%n+1;
r=(r+ans-1)%n+1;
if(l>r) swap(l, r);
ans=val[query(l, r)];
printf("%d\n", ans);
}
}

问题描述

有一个1-n的序列,你只知道在每一位之前有多少个数字比它小,请你求出这个序列。

输入

第一行n, 接下来n-1行,第i行表示在第i个数之前有多少个数比它小。

输出

输出这个序列

思路

不难想到,这个序列我们可以从后往前推。我们设num[i]为第i个数之前有多少个数比它小。

因为最后一个数有num[n]个比它小,那它一定是num[n]+1。然后继续递推倒数第n-1个数,它就是除了num[n]+1中的第num[n-1]+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
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 8005
using namespace std;

int n, num[MAXN], t[MAXN], ans[MAXN];

void add(int x, int y) {for(; x<=n; x+=(x&-x)) t[x]+=y;}
int sum(int x) {int y=0; for(; x; x-=(x&-x)) y+=t[x]; return y;}

int search(int x)
{
int l=1, r=n, ans;
while(l<=r)
{
int mid=(l+r)/2;
if(sum(mid)>=x) ans=mid, r=mid-1;
else l=mid+1;
}
return ans;
}

int main()
{
scanf("%d", &n);
add(1, 1);
for(int i=2; i<=n; i++)
{
scanf("%d", &num[i]);
add(i, 1);
}
for(int i=n; i>=1; i--)
{
ans[i]=search(num[i]+1);
add(ans[i], -1);
}
for(int i=1; i<=n; i++) printf("%d\n", ans[i]);
}

线段树

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
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 8005
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
using namespace std;

int n, num[MAXN], t[MAXN*4], ans[MAXN];

void build(int rt, int l, int r)
{
t[rt]=r-l+1;
if(l==r) return;
build(ls, l, mid);
build(rs, mid+1, r);
}

int query(int rt, int l, int r, int x)
{
t[rt]--;
if(l==r) return l;
if(x>t[ls]) return query(rs, mid+1, r, x-t[ls]);
else return query(ls, l, mid, x);
}

int main()
{
scanf("%d", &n);
build(1, 1, n);
for(int i=2; i<=n; i++) scanf("%d", &num[i]);
for(int i=n; i>=1; i--) ans[i]=query(1, 1, n, num[i]+1);
for(int i=1; i<=n; i++) printf("%d\n", ans[i]);
}

6月3日 AtCoder Grand Contest 025 题目链接:https://agc025.contest.atcoder.jp/assignments

A - Digits Sum

直接枚举就好了,不多说。

B - RGB Coloring

思路: 思路比较巧妙。我们可以将题意进行转化,你可以在n层中选取任意a层涂红,再从n层中选取任意b层涂蓝(如果一层既涂成红,又涂成蓝,那就相当与原题中的涂成绿)。

这样就把原来三种颜色变成两种。于是只要枚举a, b,答案就是之和,其中满足A*a+B*b=K。

代码:

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
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define LL long long
#define MAXN 300005
#define p 998244353
using namespace std;

LL n, a, b, k, ans, fac[MAXN], inv[MAXN];

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

void init()
{
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
for(int i=2; i<=n; i++) inv[i]=(p-p/i)*inv[p%i]%p;
for(int i=2; i<=n; i++) fac[i]=fac[i-1]*i%p;
for(int i=2; i<=n; i++) inv[i]=inv[i-1]*inv[i]%p;
}

LL com(int n, int m)
{
if(m>n) return 0;
return fac[n]*inv[n-m]%p*inv[m]%p;
}

int main()
{
scanf("%lld%lld%lld%lld", &n, &a, &b, &k);
if(k==0) {printf("1\n"); return 0;}
init();
for(int i=1; a*i<=k && i<=n; i++)
{
if((k-a*i)%b!=0) continue;
int j=(k-a*i)/b;
ans=(com(n, i)*com(n, j)%p+ans)%p;
}
printf("%lld\n", ans);
}

C - Interval Game

思路: 这是一个贪心,每次Aoki一定会选择让Takahashi行走距离最长的区间,而Takahashi一定会走到区间上最靠近自己的端点(如果Takahashi在区间中,则不动)。

因此,Aoki会选择左端点最右或右端点最左的区间,他轮流选择 左区间和右区间,这样使得Takahashi走的最长。

长度就比较好计算(细节见代码),但要注意由于起点和终点在原点,所以要把[0,0]这个区间加入进去。

代码:

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

LL n, ans, l[MAXN], r[MAXN];

bool CMP1(LL x, LL y) {return x>y;}
bool CMP2(LL x, LL y) {return x<y;}

int main()
{
scanf("%lld", &n);
for(int i=1; i<=n; i++) scanf("%lld%lld", &l[i], &r[i]);
sort(l, l+n+1, CMP1);
sort(r, r+n+1, CMP2);
for(int i=0; i<=n; i++) if(l[i]>r[i]) ans+=l[i]-r[i];
printf("%lld\n", ans*2);
}

D - Choosing Points

思路: 这其实是一个二分图染色,假设一个点为白色,那么我们可以将与它相距$\sqrt{D}$的点染成黑色,那么这样永远不会有两种颜色相同的点距离为$\sqrt{D}$(由于是二分图,染色是不会冲突)。二分图的证明可以看原题题解,证明了一大堆

于是我们可以依据D1和D2,将这个图进行两次染色。我们可以根据我们染的颜色,确定出n^2个点。原题题解也证明了保证有n^2个点符合要求。 代码:

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
#include <bits/stdc++.h>
#define MAXN 600
using namespace std;

int n, m, cnt, d[5], col[MAXN][MAXN][2];
bool vis[MAXN][MAXN][2];

void dfs(int x, int y, int a, int b)
{
if(x<0 || x>m || y<0 || y>m || vis[x][y][a]) return;
vis[x][y][a]=true; col[x][y][a]=b;
for(int i=0; i*i<=d[a]; i++)
{
int j=sqrt(d[a]-i*i);
if(i*i+j*j!=d[a]) continue;
dfs(x+i, y+j, a, 3-b);
dfs(x-i, y+j, a, 3-b);
dfs(x+i, y-j, a, 3-b);
dfs(x-i, y-j, a, 3-b);
}
}

void debug()
{
for(int i=0; i<=m; i++)
{
for(int j=0; j<=m; j++) printf("%d ", col[i][j][0]);
printf("\n");
}
printf("\n\n");
for(int i=0; i<=m; i++)
{
for(int j=0; j<=m; j++) printf("%d ", col[i][j][1]);
printf("\n");
}
}

int main()
{
scanf("%d%d%d", &n, &d[0], &d[1]);
m=n*2-1;
for(int i=0; i<=m; i++)
for(int j=0; j<=m; j++)
{
if(!vis[i][j][0]) dfs(i, j, 0, 1);
if(!vis[i][j][1]) dfs(i, j, 1, 1);
}
for(int i=0; i<=m; i++)
for(int j=0; j<=m; j++)
{
if(col[i][j][0]==1 && col[i][j][1]==1)
{
printf("%d %d \n", i, j);
if(++cnt==n*n) return 0;
}
}
}