[比赛|考试] 咕咕掉的一些比赛总结
咕咕掉1:9月9日上午nowcoder tg组1
咕咕掉2:9月14日晚上wannafly
(以上是没有写总结的比赛,考得都很惨,更新不更新看心情,如果有更新会在本文里更新的)
9.12 下午校内
160pts/300pts,rank4,(60,60,40)
经验/教训:T1 WA4点-->多组数据未完全清空;T2未AC-->脑洞不大;T3少拿20Pts-->时间复杂度分析出问题
railway
仓鼠找Sugar+多组数据。直接倍增LCAxjb乱判判就行了,因为是多组数据所以每次要Memset,不过本人作死在邻接表加边里用static,只有这个没被清空,所以邻接表就爆炸了,WA了四个点。以后要注意!
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
void read(int &x)
{
static char ch;
x = 0;
ch = getchar();
while (!isdigit(ch))
ch = getchar();
while (isdigit(ch))
{
x = x * 10 + ch - 48;
ch = getchar();
}
}
struct edge
{
int v, ne;
}a[200010];
int h[100010], d[100010], fa[100010][19];//17
int n, m;
int tmp;
void add(int x, int y)
{
a[++tmp] = (edge){y, h[x]};
h[x] = tmp;
}
void search(int x)
{
for (int i = h[x]; i != 0; i = a[i].ne)
{
if (a[i].v != fa[x][0])
{
fa[a[i].v][0] = x;
d[a[i].v] = d[x] + 1;
search(a[i].v);
}
}
}
int lca(int x, int y)
{
if (d[x] < d[y])
swap(x, y);
int dis = d[x] - d[y];
for (int i = 0; dis > 0; i++, dis >>= 1)
if (dis & 1)
x = fa[x][i];
if (x == y)
return x;
for (int i = 18; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
{
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
void work()
{
read(n);
read(m);tmp = 0;
memset(h, 0, sizeof(h));
memset(a, 0, sizeof(a));
for (int x, y, i = 1; i < n; i++)
{
read(x);
read(y);
add(x, y);
add(y, x);
}
memset(d, 0, sizeof(d));
memset(fa, 0, sizeof(fa));
d[1] = 1;
search(1);
for (int j = 1; j <= 18; j++)
for (int i = 1; i <= n; i++)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
for (int x1, y1, x2, y2, i = 1; i <= m; i++)
{
read(x1);
read(y1);
read(x2);
read(y2);
int lc1 = lca(x1, y1);
int lc2 = lca(x2, y2);
if (d[lc1] < d[lc2])
{
swap(lc1, lc2);
swap(x1, x2);
swap(y1, y2);
}
if (d[lc1] > d[x2] && d[lc1] > d[y2])
printf("NO\n");
else
{
if (lca(lc1, x2) == lc1 || lca(lc1, y2) == lc1)
printf("YES\n");
else
printf("NO\n");
}
}
}
int main()
{
freopen("railway.in", "r", stdin);
freopen("railway.out", "w", stdout);
int T;
read(T);
while (T--)
work();
fclose(stdin);
fclose(stdout);
return 0;
}
count
给定N的一个随机排列,求\(\displaystyle \sum_{l=1}^N\sum_{r=l}^N(a[i]-a[j]),l\le i,j\le r\)的最大值。
转换一下即为\(\displaystyle \sum_{l=1}^N\sum_{r=l}^N(\max(l,r)-\min(l,r))\),再转换一下即为\(\displaystyle \sum_{l=1}^N\sum_{r=l}^N\max(l,r)-\sum_{l=1}^N\sum_{r=l}^N\min(l,r)\),转化为了两个子问题分别求解。考虑每个数,它能成为最大值的区间为他往左延伸到第一个比他大的位置(设为l[i])到他往右延伸到第一个比他大的位置(设为r[i]),一开始左右两边设两个极大值。他对答案的贡献为\((r[i]-i)*(i-l[i])*a[i]\),最后把所有的这个相加即可。r和l可以用单调栈求出,复杂度为\(O(n)\),辣鸡题解的是\(O(n\log n)\)的单调队列,感谢cjh的单调栈做法。最小值类似做法。(我的60分做法是稀疏表或者分治法)
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
inline void read(int &x)
{
static char ch;
x = 0;
ch = getchar();
while (!isdigit(ch))
ch = getchar();
while (isdigit(ch))
{
x = x * 10 + ch - 48;
ch = getchar();
}
}
int n;
long long ans;
int a[100010];
int tmp[100010];
int r[100010], l[100010], s[100010], top;
long long query_max()
{
a[0] = 0x3f3f3f3f;
a[n + 1] = 0x3f3f3f3f;
top = 0;
memset(s, 0, sizeof(s));
memset(r, 0, sizeof(r));
for (int i = 1; i <= n + 1; i++)
{
while (top > 0 && a[s[top]] < a[i])
r[s[top--]] = i;
s[++top] = i;
}
top = 0;
memset(s, 0, sizeof(s));
memset(l, 0, sizeof(l));
for (int i = n; i >= 0; i--)
{
while (top > 0 && a[s[top]] < a[i])
l[s[top--]] = i;
s[++top] = i;
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
ans += 1LL * (r[i] - i) * (i - l[i]) * a[i];
}
return ans;
}
long long query_min()
{
top = 0;
a[0] = -2000000000;
a[n + 1] = -2000000000;
memset(s, 0, sizeof(s));
memset(r, 0, sizeof(r));
for (int i = 1; i <= n + 1; i++)
{
while (top > 0 && a[s[top]] > a[i])
r[s[top--]] = i;
s[++top] = i;
}
top = 0;
memset(s, 0, sizeof(s));
memset(l, 0, sizeof(l));
for (int i = n; i >= 0; i--)
{
while (top > 0 && a[s[top]] > a[i])
l[s[top--]] = i;
s[++top] = i;
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
ans += 1LL * (r[i] - i) * (i - l[i]) * a[i];
}
return ans;
}
void work()
{
read(n);
for (int i = 1; i <= n; i++)
read(a[i]);
printf("%lld\n", query_max() - query_min());
}
int main()
{
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
int T;
read(T);
while (T--)
work();
fclose(stdin);
fclose(stdout);
return 0;
}
treecnt
\((T3\approx T1+T2)\)给定N个节点的树,我们令\(f(l,r)\)表示让l~r这些节点联通需要连的边数,求\(\displaystyle \sum_{l=1}^N\sum_{r=l}^Nf(l,r)\)。这题60pts是枚举l,每次从l开始dfs一下处理出来一些东西,然后枚举r,判断新加入的r是否在生成树上,如果不在,暴力把root(也就是l)到r的路径更新。我打的40pts按照老套路,先dfs然后lca,多一个log。一下是60pts的代码。
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
void read(int &x)
{
static char ch;
x = 0;
ch = getchar();
while (!isdigit(ch))
ch = getchar();
while (isdigit(ch))
{
x = x * 10 + ch - 48;
ch = getchar();
}
}
struct edge
{
int v, ne;
}a[200010];
int vis[100010], h[100010], d[100010], fa[100010];//17
int n, tot, ans;
bool use[100010];
void access(int s, int t)
{
// printf("access %d -> %d\n", t, s);
for (int i = t; i != s; i = fa[i])
{
if (use[i] == false)
{
use[i] = true;
tot++;
}
vis[i] = true;
}
vis[s] = true;
}
void add(int x, int y)
{
static int tmp = 1;
a[++tmp] = (edge){y, h[x]};
h[x] = tmp;
}
void search(int x)
{
for (int i = h[x]; i != 0; i = a[i].ne)
{
if (a[i].v != fa[x])
{
fa[a[i].v] = x;
d[a[i].v] = d[x] + 1;
search(a[i].v);
}
}
}
int main()
{
freopen("treecnt.in", "r", stdin);
freopen("treecnt.out", "w", stdout);
read(n);
for (int x, y, i = 1; i < n; i++)
{
read(x);
read(y);
add(x, y);
add(y, x);
}
for (int l = 1; l <= n; l++)
{
tot = 0;
memset(vis, 0, sizeof(vis));
memset(use, 0, sizeof(use));
memset(fa, 0, sizeof(fa));
d[l] = 1;
search(l);
vis[l] = true;
for (int r = l + 1; r <= n; r++)
{
if (vis[r] == 0)
{
access(l, r);
}
// printf("use[%d, %d] = %d\n", l, r, tot);
ans += tot;
}
}
printf("%d\n", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
nowcoder OI赛制测试赛3
牛客网OI赛制测试赛3
460/600(100,100,40,20,100,100),Rank46
总结/经验教训:
A了4个题(虽然有一个用py,虽然Noip不资磁py,但你敢告诉我考场上不让用py你抗揍就行)
炸了两个,一个是tm矩阵乘法写炸了,答案矩阵一开始应该是零矩阵写成单位矩阵了。。还有一个是贪心,没写baoli,(想想wzp的惨案)
以下是题解
T1
小a有一个n位的数字,但是它忘了各个位上的数是什么,现在请你来确定各个位上的数字,满足以下条件:
设第i位的数为ai,其中a1为最高位,an为最低位,K为给定的数字
1.不含前导0
2.\(\displaystyle(\sum_{i=1}^n(a_i-a_{i-1}))=K\)
输入K,求满足条件的方案数 \(\pmod{1\mathrm e9+7}\),无解输出0
把2中的式子展开,就变成了\(a_n-a_1=K\),
当n=1,则有\(a_1-a_1=K\),则有\(K=0\),故\(K=0\)输出9,否则输出0。
当\(n\neq1\),则中间有\(n-2\)位随便填数,为\(10^{n-2}\)种,左右两边的数相关。当\(K\ge0\),也就是末位的数字比首位数字大K,由于首位不能为0,所以有\(9-K\)个方案。\(K<0\),末尾的数字一定比首位数字小,所以考虑末尾数字可以为\(0\)到\(9+K\),一共\(10+K\)方案。然后打一个快速幂就行了。
#include <bits/stdc++.h>
#define p 1000000007
#define int long long
using namespace std;
int qpow(int x, int y)
{
int ans = 1;
while (y > 0)
{
if (y & 1)
ans = ans * x % p;
x = x * x % p;
y >>= 1;
}
return ans;
}
signed main()
{
int n, k;
cin >> n >> k;
if (n == 1)
{
if (k == 0)
{
cout << 9 << endl;
}
else
{
cout << 0 << endl;
}
return 0;
}
int ans = qpow(10, n - 2);
//K = 0 -> 9
//K = 1 -> 8
//K = 2 -> 7
//K = -1 -> 9
//K = -2 -> 8
if (k < 0)
{
if (k <= -10)
{
cout << 0 << endl;
return 0;
}
int f = 10 + k;
cout << ans * f % p << endl;
}
else
{
if (k >= 9)
{
cout << 0 << endl;
return 0;
}
int f = 9 - k;
cout << ans * f % p << endl;
}
return 0;
}
T2
小a有N个数\(a_1,a_2,\cdots,a_N\),给出\(q\)个询问,每次询问给出区间\([L, R]\),现在请你找到一个数\(X\),使得 1、\(0\le x<2^{31}\) 2、\(\displaystyle \sum_{i=L}^RX\oplus a[i]\)最大,\(\oplus\)表示异或操作,若有多组可行解,请输出较小的解
然后和这题我们发现首先数的二进制位是不相干的,可以拆开来做,然后,对于某一位,有些数是0,有些数是1,我们现在要找一个数让他去异或这些数,并求和。如果要异或的X是1,0会变成1,1会变成0,显然,如果0的个数比1大,就选择异或1,否则就是0。由于输出较小的解,二者数量相同就选择0。0和1的数量可以通过前缀和来搞。
#include <bits/stdc++.h>
using namespace std;
int qz[100010][32][2];
int a[100010], n;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
for (int j = 0; j < 31; j++)
{
if (a[i] & (1 << j))
{
qz[i][j][1] = 1;
}
else
{
qz[i][j][0] = 1;
}
qz[i][j][1] += qz[i - 1][j][1];
qz[i][j][0] += qz[i - 1][j][0];
}
}
int q;
scanf("%d", &q);
for (int i = 1; i <= q; i++)
{
int l, r;
scanf("%d%d", &l, &r);
int ans = 0;
for (int j = 0; j < 31; j++)
{
int cnt0 = qz[r][j][0] - qz[l - 1][j][0];
int cnt1 = qz[r][j][1] - qz[l - 1][j][1];
if (cnt1 < cnt0)
ans |= (1 << j);
}
printf("%d\n", ans);
}
return 0;
}
T3
有一天clccle在机房里和sarlendy玩游戏,游戏的规则是这样的,在clccle和sarlendy的面前有两行长度为2n的硬币序列,共4n枚硬币,从clccle开始取,每次只能取两个人之前都没取过的位置的硬币,如果所取硬币朝上(U)的话记为1,所取硬币朝下(D)的话记为0,这样n次后两个人就得到了长度为n的数字串,谁的数字大谁就赢了,当然也存在平局的情况,当然这两个人都非常的睿智,现在clccle想知道,Ta有没有必胜策略?如果有的话就输出“clccle trl!”,没有的话输出“sarlendy tql!”,特别的,平局输出“orz sarlendy!”。
博弈论,这道题可以直接贪心思想。每个人肯定先选择(1,1),然后选择自己能拿硬币的(你不拿让人家拿你就炸了),然后选择对方能拿硬币的(阻止对方拿硬币),其实是贪心+模拟
#include <bits/stdc++.h>
using namespace std;
char s1[2000010], s2[2000010];
int n;
int main()
{
const string Awin = "clccle trl!\n";
const string Bwin = "sarlendy tql!\n";
const string Draw = "orz sarlendy!\n";
scanf("%d", &n);
if (n > 0)
{
scanf("%s%s", s1, s2);
int tot[4];
for (int i = 0; i < 2 * n; i++)
{
if (s1[i] == 'D' && s2[i] == 'D')
tot[0]++;
if (s1[i] == 'D' && s2[i] == 'U')
tot[1]++;
if (s1[i] == 'U' && s2[i] == 'D')
tot[2]++;
if (s1[i] == 'U' && s2[i] == 'U')
tot[3]++;
}
for (int i = 1; i <= n; i++)
{
int A = 0, B = 0;
//Get A's choice
if (tot[3] > 0)
{
tot[3]--;
A = 1;
}
else if (tot[2] > 0)
{
tot[2]--;
A = 1;
}
else if (tot[1] > 0)
{
tot[1]--;
}
else if (tot[0] > 0)
{
tot[0]--;
}
//Get B's choice
if (tot[3] > 0)
{
tot[3]--;
B = 1;
}
else if (tot[1] > 0)
{
tot[1]--;
B = 1;
}
else if (tot[2] > 0)
{
tot[2]--;
}
else if (tot[0] > 0)
{
tot[0]--;
}
if (A > B)
{
printf(Awin.c_str());
return 0;
}
if (A < B)
{
printf(Bwin.c_str());
return 0;
}
}
}
printf(Draw.c_str());
return 0;
}
T4
tmd矩阵乘法写炸了。。。
qn是个特别可爱的小哥哥,qy是个特别好的小姐姐,他们两个是一对好朋友 [ cp (划掉~)又是一年嘤花烂漫时,小qn于是就邀请了qy去嘤花盛开的地方去玩。当qy和qn来到了田野里时,qy惊奇的发现,嘤花花瓣以肉眼可见的速度从树上长了出来。仔细看看的话,花瓣实际上是以一定规律长出来的,而且,每次张成新的花瓣的时候,上一次的花瓣就会都落到地上,而且不会消失。花瓣生长的规律是,当次数大于等于2时,第i次长出来的花瓣个数和上一次张出来的花瓣个数的差是斐波那契数列的第i-1项。初始的时候地上没有花瓣,树上的花瓣个数为1,第一次生长的花瓣个数为1。初始的那个花瓣就落到了地上现在,小qn想知道,经过k次生长之后,树上和地上的总花瓣个数是多少? ps:斐波那契数列: f[1]=f[2]=1;f[i]=f[i-1]+f[i-2] (\(i\ge2\)且\(i\in N_+\))
然后呢这题找规律,1,2,4,7,12,20,33。。。。然后我们惊奇的发现\(f[1]=1\),\(f[2]=2\),\(f[n]=f[n-1]+f[n-2]+1\),然后现在输入n求f得n+1项。
所以说这个其实很简单,直接构建矩阵,像这种非齐次的在后面直接加一个1就行了。
\(\begin{pmatrix}1&1&1\\1\\&&1\end{pmatrix}\begin{pmatrix}f[n]\\f[n-1]\\1\end{pmatrix}=\begin{pmatrix}f[n+1]\\f[n]\\1\end{pmatrix}\)
直接对左边的上快速幂。注意特判一下N=0
#include <bits/stdc++.h>
#define p 998244353
#define int long long
using namespace std;
struct matrix
{
int a[3][3];
matrix(int type = 0)
{
memset(a, 0, sizeof(a));
a[0][0] = a[1][1] = a[2][2] = type;
}
friend matrix operator*(const matrix &a, const matrix &b);
};
matrix operator*(const matrix &a, const matrix &b)
{
matrix ans(0);
for (int k = 0; k <= 2; k++)
for (int i = 0; i <= 2; i++)
for (int j = 0; j <= 2; j++)
(ans.a[i][j] += a.a[i][k] * b.a[k][j]) %= p;
return ans;
}
matrix qpow(matrix a, int b)
{
matrix ans(1);
while (b > 0)
{
if (b & 1)
ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
signed main()
{
int n;
scanf("%lld", &n);
matrix d;if(n==0){puts("1");return 0;}
d.a[0][0] = d.a[0][1] = d.a[0][2] = d.a[1][0] = d.a[2][2] = 1;
matrix x;
x.a[0][0] = 2;
x.a[1][0] = x.a[2][0] = 1;
printf("%lld\n", (qpow(d, n - 1) * x).a[0][0]);
return 0;
}
T5
给定两个整数N,M表示区间\([2^N,2^M)\),求在这个区间里有多少个整数i满足\(i\!\!\!\mod 7=1\),\(N,M\le65\)
这题其实很简单,除以7取商即可。数据范围要开long long还可能出锅(我是锅王),我就用了自带高精的python写的。注意//
在C++里是注释,在python是整除。/
在python是小数除法。
def work(x):
x = (1 << x)
if x <= 1:
return 0
return (x - 2) // 7 + 1
def main():
s = input().split()
print(work(int(s[1])) - work(int(s[0])))
main()
T6
(艾特某大佬)
小可爱是个可爱的女孩子(nzdl)。
众所周知,小可爱在物竞初赛时候有两道大题没有做出来,所以,可爱的小可爱(qwq)便沉浸在了毒瘤之中——无法接受在任何地方看到"suqingnianloveskirito"这个东西。然而,这时候从SD某处送来了一封安慰信(情书),信的内容是一个26个小写拉丁字母组成的字符串s。这封信提前被wyxdrqc劫了下来(没错,就是这个劫),他打开了这封信,结果发现了满篇的"suqingnianloveskirito"所以他想篡改这封信。
由于他的能力有限,所以他只能把这个字符串的其中两个位置上的字符互换,而且只能操作一次。
他现在想问你,通过他的操作能不能使"suqingnianloveskirito"不是这个字符串的子串。
这个题其实很简单,用C++库函数的find连续找三次,如果三次都找到了,肯定不行了。如果找到了两次,第三次返回string::npos
,则前两次子串交换第一个串的第一个和第二个串的第二个即可完成任务。如果第二次也返回string::npos
,则只把第一个串乱搞搞就行了,如果第一次就是返回string::npos
,如果你乱搞,可能搞出来一个suqingnianloveskirito,所以我就rand了一下(反正有spj),每次交换判断一下(其实一般都能判过的)。
#include <bits/stdc++.h>
using namespace std;
int main()
{
string str;
string fk = "suqingnianloveskirito";
getline(cin, str);
size_t ans1 = str.find(fk);
size_t ans2 = str.find(fk, ans1 + 1);
size_t ans3 = str.find(fk, ans2 + 1);
// printf("[%llu] [%llu] [%llu]\n", ans1, ans2, ans3);
if (ans1 != string::npos && ans2 != string::npos && ans3 != string::npos)
{
printf("No\n");
return 0;
}
if (ans1 != string::npos && ans2 != string::npos)
{//swap ans1's s and ans2's u
cout << "Yes\n" << ans1 + 1 << ' ' << ans2 + 2 << endl;
return 0;
}
if (ans1 != string::npos)
{
cout << "Yes\n" << ans1 + 1 << ' ' << ans1 + 2 << endl;
return 0;
}
while (1)
{
size_t t1 = rand() % str.length();
size_t t2 = rand() % str.length();
string str1(str);
swap(str1[t1], str1[t2]);
if (str1.find(fk) == string::npos)
{
cout << "Yes\n" << t1 + 1 << ' ' << t2 + 1 << endl;
return 0;
}
}
return 0;
}