noip2020题解
T1
注意要先除后乘
懒得写高精
code:
#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
struct edge {
int v, nxt;
} e[N << 1];
int p[N], eid;
int init() {
memset(p, -1, sizeof p);
eid = 0;
}
void insert(int u, int v) {
e[eid].v = v;
e[eid].nxt = p[u];
p[u] = eid ++;
}
ll gcd(ll x, ll y) {
// printf("%lld %lld\n", x, y);
return x? gcd(y % x, x) : y;
}
ll a[N], b[N];
int n, m, in[N], ha[N];
queue<int> q;
vector<int> g[N];
void bfs() {
for(int i = 1; i <= n; i ++) a[i] = 0, b[i] = 1;
for(int i = 1; i <= n; i ++)
if(!in[i]) q.push(i), a[i] = 1;
while(q.size()) {
int u = q.front(); q.pop();
int k = g[u].size();
for(int i = 0; i < k; i ++) {
int v = g[u][i];
ll aa = a[u], bb = b[u];
ll d = gcd(k, aa);
bb *= (k / d);
aa /= d;
d = gcd(aa, bb);
if(d) aa /= d, bb /= d;
ll cc = a[v], dd = b[v];
d = gcd(bb, dd);
b[v] = bb / d * dd;
a[v] = cc * (b[v] / dd) + (b[v] / bb) * aa;
d = gcd(a[v], b[v]);
if(d) a[v] /= d, b[v] /= d;
in[v] --;
if(!in[v]) q.push(v);
}
}
}
int main() {
// printf("%lld\n", gcd(4, 6));
// freopen("water.in","r",stdin);
// freopen("water.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) {
int k;
scanf("%d", &k);
for(int j = 1; j <= k; j ++) {
int v;
scanf("%d", &v);
in[v] ++;
g[i].push_back(v);
}
if(!k) ha[i] = 1;
}
bfs();
for(int i = 1; i <= n; i ++) if(ha[i]) {
printf("%lld %lld\n", a[i], b[i]);
}
return 0;
}
T2
考虑
z
z
z函数
可以发现,对于一个前缀
S
1...
i
S_{1...i}
S1...i,对应的
z
[
i
+
1
]
z[i+1]
z[i+1]表示的是
S
i
+
1....
n
S_{i+1....n}
Si+1....n的最长公共前缀
那么这个长度
z
[
i
+
1
]
/
i
+
1
z[i+1]/i +1
z[i+1]/i+1就是
S
1...
i
S_{1...i}
S1...i重复的次数
发现不管复刻几次,
F
(
C
)
F(C)
F(C)的值只有两个,一个是去掉第一个前缀的,一个是整个串的
然后直接做的行了
code:
#include<bits/stdc++.h>
#define mod1 1000000007
#define mod2 1000000009
#define ll long long
#define N 1300005
using namespace std;
int t, n, gs[35];
char st[N];
int mi1[N], mi2[N], m1[N], m2[N], a[N], g[N], z[N];
ll f[N][28];
void get() {
for(int i = 0; i <= n; i ++) z[i] = 0;
z[1] = n; int len = 0, k = 0;
for(int i = 2; i <= n; i ++) {
if(i <= len) z[i] = min(z[i - k + 1], len - i + 1);
while(i + z[i] < n && a[i + z[i]] == a[z[i] + 1]) ++ z[i];
if(i + z[i] - 1 > len) len = i + z[i] - 1, k = i;
}
}
int main() {
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
scanf("%d", &t);
while(t --) {
scanf("%s", st + 1);
n = strlen(st + 1);
for(int i = 1; i <= n; i ++) a[i] = (st[i] - 'a' + 1);
get();
// for(int i = 1; i <= n; i ++) printf("%d ", z[i]); printf("\n");
for(int i = 1; i <= 26; i ++) gs[i] = 0;
for(int i = n; i >= 1; i --) {
g[i] = g[i + 1];
gs[a[i]] ^= 1;
if(gs[a[i]]) g[i] ++;
else g[i] --;
}
for(int i = 1; i <= 26; i ++) gs[i] = 0;
int gss = 0;
for(int i = 1; i <= n; i ++) {
gs[a[i]] ^= 1;
if(gs[a[i]]) gss ++;
else gss --;
f[i][gss] ++;
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= 26; j ++)
f[i][j] += f[i][j - 1];
for(int j = 0; j <= 26; j ++)
f[i][j] += f[i - 1][j];
}
ll ans = 0;
for(int i = 1; i <= n - 1; i ++) {
/*for(int i = len; i < n; i += len) {
int hou = g[i + 1];
ans += f[len - 1][hou];
}*/
int ha = min(z[i + 1], n - i - 1) / i + 1;
int s1 = (ha + 1) / 2, s2 = ha / 2;
// printf("%d %d %d\n", i, s1, s2);
int hou = g[i + 1];
ans += 1ll * f[i - 1][hou] * s1;
int zg = g[1];
ans += 1ll * f[i - 1][zg] * s2;
// printf("%lld\n", ans);
}
printf("%lld\n", ans);
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= 26; j ++)
f[i][j] = 0;
for(int i = 0; i <= n + 1; i ++) g[i] = m1[i] = m2[i] = mi1[i] = mi2[i] = 0;
}
return 0;
}
T3
首先考虑只有两种颜色怎么搞
先考虑一个操作,把当前栈的白色全部移到上面:
假设有三个栈,
x
,
y
,
z
,
z
是
空
的
,
另
外
两
个
是
满
的
x,y,z,z是空的,另外两个是满的
x,y,z,z是空的,另外两个是满的
对
y
y
y进行操作,假设
y
y
y有
a
a
a个白色
分几步来做
操作1:
- 1、把 x x x的上面 a a a个移到 z z z上
- 2、对于 y y y的每个颜色,如果是白色就移到 x x x里,否则移到 z z z里,把 y y y移空
- 3、把 z z z中的 m − a m-a m−a个移动回 y y y中
- 4、把 x x x中的 a a a个白色移动到 y y y里
- 5、把
z
z
z中剩下的
a
a
a个移动回
x
x
x
可以发现 z z z仍然是空的, x x x中的顺序也没有变,只有 y y y的白色被提到了上面
这个操作的代码很简单:
code:
// em 表示empty,就是空栈,钦定为最后一个就好了
// a 是栈,s表示x栈中有s个白色
void workk(int x, int y, int s) {
for(int i = 1; i <= s; i ++) mv(y, em);
for(int i = 1; i <= m; i ++) {
int co = col[a[x][gs[x]]];
if(!co) mv(x, y);
else mv(x, em);
}
for(int i = 1; i <= m - s; i ++) mv(em, x);
for(int i = 1; i <= s; i ++) mv(y, x);
for(int i = 1; i <= s; i ++) mv(em, y);
}
非常简单
然后再考虑怎么把一个颜色全部移到同一个栈里
操作2:
- 1、对两个栈都做上面那个操作1,使得白色都在栈的最上面
- 2、把这两个栈上的白色全部移到空栈
- 3、然后再把其中一个栈的全部移到另一个栈
- 4、把全白的栈上的全部移到目前的空栈(当然我这么写是为了方便,其实可以重新钦定一个空栈,因为我的空栈固定是n+1,所以要多做一步)
code:
int work(int x, int y) {
int s1 = count(x, 0), s2 = count(y, 0); //数x栈,y栈中白色的个数
workk(x, y, s1), workk(y, x, s2);//
for(int i = 1; i <= s1; i ++) mv(x, em);
for(int i = 1; i <= min(s2, m - s1); i ++) mv(y, em);
for(int i = 1; i <= min(s2, m - s1); i ++) mv(x, y);
for(int i = 1; i <= min(m, s1 + s2); i ++) mv(em, x);
if(s1 + s2 >= m) return 0;//这个有什么用待会讲
return 1;
}
这样就可以把白色的移到同一个栈里,同理另一个栈就是黑色的
然后考虑这题怎么做
考虑分治,把颜色
<
=
n
/
2
<=n/2
<=n/2的染成白色,把另一边染成黑色,然后把白色的全部移到一边,黑色的全部移到一遍,然后分治下去
具体怎么做呢?
可以发现一个显然的性质,如果两个栈的白色加起来不到
m
m
m,那么黑色的加起来一定大于
m
m
m
拿两个指针指着前一半的栈和后一半的栈,假设是
x
,
y
x,y
x,y,
首先还是用操作2把白色的全部移到
x
x
x
如果白色不够m怎么办?
这里就是上面代码没有讲的地方,白色不够说明黑色一定够,那么把白色全部移到
x
x
x之后
y
y
y一定全都是黑色的
所以移完
y
+
+
y++
y++即可,反过来同理
x
+
+
x++
x++
其实就是相当于一个归并排序的操作,具体可以看代码:
code:
#include<bits/stdc++.h>
#define N 405
using namespace std;
int em, gs[N], a[N][N], ans[820000 * 3][2], anss, col[N], id[N], n, m;
int count(int x, int o) {
int ret = 0;
for(int i = 1; i <= gs[x]; i ++) ret += col[a[x][i]] == o;
return ret;
}
void mv(int x, int y) { //printf("%d %d\n", x, y);
a[y][++ gs[y]] = a[x][gs[x]];
gs[x] --;
++ anss; ans[anss][0] = x, ans[anss][1] = y;
}
void workk(int x, int y, int s) {
for(int i = 1; i <= s; i ++) mv(y, em);
for(int i = 1; i <= m; i ++) {
int co = col[a[x][gs[x]]];
if(!co) mv(x, y);
else mv(x, em);
}
for(int i = 1; i <= m - s; i ++) mv(em, x);
for(int i = 1; i <= s; i ++) mv(y, x);
for(int i = 1; i <= s; i ++) mv(em, y);
}
int work(int x, int y) { //printf(" %d %d\n", x, y);
int s1 = count(x, 0), s2 = count(y, 0);
workk(x, y, s1), workk(y, x, s2);
for(int i = 1; i <= s1; i ++) mv(x, em);
for(int i = 1; i <= min(s2, m - s1); i ++) mv(y, em);
for(int i = 1; i <= min(s2, m - s1); i ++) mv(x, y);
for(int i = 1; i <= min(m, s1 + s2); i ++) mv(em, x);
if(s1 + s2 >= m) return 0;
return 1;
}
void solve(int *id, int n) {
if(n == 1) return;
int mid = n / 2;
for(int i = 1; i <= mid; i ++) col[id[i]] = 0;
for(int i = mid + 1; i <= n; i ++) col[id[i]] = 1;
int x = 1, y = mid + 1;
while(x <= mid && y <= n) {
while(count(id[x], 0) == m && x <= mid) x ++;
while(count(id[y], 1) == m && y <= n) y ++;
if(x > mid || y > n) break;
if(work(id[x], id[y])) y ++;
else x ++;
}
solve(id, mid), solve(id + mid, n - mid);
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
scanf("%d", &a[i][j]), gs[i] = m;
for(int i = 1; i <= n; i ++) id[i] = i;
em = n + 1;
solve(id, n);
printf("%d\n", anss);
for(int i = 1; i <= anss; i ++)
printf("%d %d\n", ans[i][0], ans[i][1]);
return 0;
}
是不是很短