bzoj刷题记录2017.4.2-4.4
bzoj刷题记录 4.2-4.4
月考完了。凭借多年应试经验连蒙带猜应付掉,然而坠稳的数学跪烂[没考过班里要学文的dalao]…还是认真刷OJ吧。
bzoj1211:[HNOI2004]树的计数
从Zars19的blog里学习了一个prufer编码。经典文章是matrix67 dalao的介绍,传送门。
总之就是把无根有标号树和长度为
然而直接组合数莫名其妙的跪,于是阶乘展开变成:
看到阶乘然后就可以上下分解质因数随便减一减就好。另外特判坑死人。
#include <bits/stdc++.h>
using namespace std;
int prime[155], tot = 0;
int n, s, cnt = 0;
int c[155];
void get_prime()
{
for (int i = 2; i <= n; i++) {
int flg = 0;
for (int j = 2; j < i; j++)
if (i%j == 0) {
flg = 1;
break;
}
if (!flg) {
prime[++tot] = i;
for (int j = i; (n-2)/j > 0; j *= i)
c[tot] += (n-2)/j;
}
}
}
void div(int nd)
{
for (int i = 1; i <= tot; i++) {
for (int j = prime[i]; j <= nd; j *= prime[i])
c[i] -= nd/j;
}
}
long long power(int a, int n)
{
if (n == 0) return 1;
long long p = power(a, n>>1);
p *= p, p *= (n&1)?a:1;
return p;
}
int main()
{
scanf("%d", &n);
get_prime();
long long ans = 1;
if (n == 1) {
int d; scanf("%d", &d);
cout << (d == 0) << endl;
return 0;
}
for (int i = 1; i <= n; i++) {
int d; scanf("%d", &d);
if (d == 0) {
cout << 0 << endl;
return 0;
}
div(d-1);
cnt += d-1;
}
if (cnt != n-2) cout << 0 << endl;
else {
for (int i = 1; i <= tot; i++) {
ans *= power(prime[i], c[i]);
}
cout << ans << endl;
}
return 0;
}
bzoj1030:文本生成器
AC自动机上dp处理不包含任何一个串的方案总数,然后减去就好。
顺便熟练一波AC自动机。
#include <bits/stdc++.h>
using namespace std;
struct ACM {
struct node {
bool finish;
int chl[26], fail;
node():finish(0),fail(0)
{ memset(chl, 0, sizeof chl); }
} tree[20005];
int root, top;
queue<int> que;
ACM():root(0),top(0){}
void push(int &nd, const char *str)
{
if (nd == 0) nd = ++top;
if (*str == '\0') tree[nd].finish = 1;
else push(tree[nd].chl[*str-'A'], str+1);
}
void push(const char *str) {push(root, str); }
void init()
{
que.push(root); tree[root].fail = 0;
while (!que.empty()) {
int tp = que.front(); que.pop();
tree[tp].finish |= tree[tree[tp].fail].finish;
for (int i = 0; i < 26; i++) {
if (!tree[tp].chl[i]) continue;
int nd = tree[tp].fail;
while (nd && !tree[nd].chl[i]) nd = tree[nd].fail;
tree[tree[tp].chl[i]].fail = nd?tree[nd].chl[i]:root;
tree[tree[tp].chl[i]].finish |= tree[tp].finish;
que.push(tree[tp].chl[i]);
}
}
}
void dfs(int nd, int tab = 0)
{
if (!nd) return;
for (int i = 1; i <= tab; i++) putchar(' ');
printf("%d(%d, %d) : ", nd, tree[nd].fail, tree[nd].finish);
for (int i = 0; i < 26; i++)
if (tree[nd].chl[i]) printf("--%c--> %d; ", i+'A', tree[nd].chl[i]);
puts("");
for (int i = 0; i < 26; i++)
if (tree[nd].chl[i]) dfs(tree[nd].chl[i], tab+2);
}
} ACM;
int dp[105][20005];
int dfs(int m, int nd)
{
if (ACM.tree[nd].finish) return 0;
if (m == 0) return 1;
if (dp[m][nd] != -1) return dp[m][nd];
dp[m][nd] = 0;
for (int i = 0; i < 26; i++) {
int p = nd;
while (p && !ACM.tree[p].chl[i]) p = ACM.tree[p].fail;
if (p) (dp[m][nd] += dfs(m-1, ACM.tree[p].chl[i])) %= 10007;
else (dp[m][nd] += dfs(m-1, ACM.root)) %= 10007;
}
return dp[m][nd];
}
int power(int a, int n)
{
if (n == 0) return 1;
int p = power(a, n>>1);
(p *= p) %= 10007;
if (n&1) (p *= a) %= 10007;
return p;
}
char str[105];
int n, m;
int main()
{
memset(dp, -1, sizeof dp);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", str);
ACM.push(str);
}
ACM.init();
int ans = power(26, m)-dfs(m, ACM.root);
((ans %= 10007) += 10007) %= 10007;
cout << ans << endl;
return 0;
}
bzoj1016:[JSOI2008]最小生成树计数
原来讲过的题然而一直没有做。
按边长从小到大排,每次做一个Matrix_Tree生成森林计数,然后把能缩的缩在一起。除了特判1A。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105, MAXM = 1005;
struct node {
int i, j, d;
friend bool operator < (const node &a, const node &b)
{ return a.d < b.d; }
} edge[MAXM];
int n, m;
int gp[MAXN], vis[MAXN], gt; // 缩在一起的点的新编号
double g[MAXN][MAXN]; // 求解行列式
struct ufs {
int fa[MAXN];
void clear()
{ memset(fa, 0, sizeof fa); }
ufs(){clear(); }
inline int findf(int nd)
{ return fa[nd]?fa[nd] = findf(fa[nd]):nd; }
void link(int i, int j)
{
int p = findf(i), q = findf(j);
if (p != q) fa[p] = q;
}
} UFS, u2;
int calc()
{
if (gt == 0) return 1;
gt--;
double ans = 1;
for (int i = 1; i <= gt; i++) {
if (g[i][i] == 0) {
int k = i+1; while (!g[k][i]) k++;
swap(g[i], g[k]);
}
ans *= g[i][i];
for (int j = i+1; j <= gt; j++) {
double t = -g[j][i]/g[i][i];
for (int k = i; k <= gt; k++)
g[j][k] += g[i][k]*t;
}
}
// cout << ans << endl;
return int(ans+0.05)%31011;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%d%d%d", &edge[i].i, &edge[i].j, &edge[i].d);
sort(edge+1, edge+m+1);
long long ans = 1;
for (int i = 1; i <= m; ) {
// cout << "--" << i <<" " << edge[i].d<< endl;
int d = edge[i].d;
memset(vis, 0, sizeof vis);
memset(g, 0, sizeof g);
u2.clear();
gt = 0;
for (int j = i; j <= m && edge[j].d == d; j++) {
int p = UFS.findf(edge[j].i), q = UFS.findf(edge[j].j);
if (p==q) continue;
if (!vis[p]) gp[p] = ++gt, vis[p] = 1;
if (!vis[q]) gp[q] = ++gt, vis[q] = 1;
g[gp[p]][gp[q]]--, g[gp[q]][gp[p]]--, g[gp[p]][gp[p]]++, g[gp[q]][gp[q]]++, u2.link(gp[p], gp[q]);
} // 建图
if (gt == 1) break;
for (int k = 1; k <= gt; k++)
for (int j = k+1; j <= gt; j++) {
// cout << k << " " << j << endl;
if (u2.findf(k) != u2.findf(j))
u2.link(k, j), g[k][j] = g[j][k] = -1, g[k][k]++, g[j][j]++;
}
// 建桥
(ans *= calc()) %= 31011; // matrix_tree theory
while (edge[i].d == d) UFS.link(edge[i].i, edge[i].j), i++; // 合并
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (UFS.findf(i) != UFS.findf(j)) {
cout << 0 << endl;
return 0;
}
cout << ans << endl;
return 0;
}
bzoj1004: [HNOI2008]Cards
居然自己看并1A了这置换群…
首先奇奇怪怪的表述让我们知道出题人要告诉我们是个置换群:
- 输入数据保证任意多次洗牌都可用这m种洗牌法中的一种代替——运算封闭;
- 置换显然满足结合律、交换律;
- 且对每种洗牌法,都存在一种洗牌法使得能回到原状态——每个元素都可逆;
- 有没有幺元你加一个不就有了吗?
然后就可以做波利亚计数了。dp预处理每个置换下不变(环内染一种色)的方案数,求和算平均。为了防止重复置换,加一个哈希判重。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 70;
int Sr, Sb, Sg, m, p, n;
int power(int a, int n)
{
if (n == 0) return 1;
int k = power(a, n>>1);
(k *= k) %= p, (k *= (n&1)?a:1) %= p;
return k;
}
int inv(int a)
{ return power(a, p-2); }
int fa[MAXN], siz[MAXN], per[MAXN];
inline int findf(int nd)
{ return fa[nd]?fa[nd] = findf(fa[nd]):nd; }
inline void link(int a, int b)
{
int p = findf(a), q = findf(b);
if (p != q) fa[p] = q, siz[q] += siz[p];
}
int ht[MAXN], gt = 0;
int dp[MAXN][21][21][21]; // dp嘛..
int cnp = 0; // |G| 波利亚计数原理
// ------
// 哈希表 防止重复而gg
int hsh[3214567];
int hash_p(int p[])
{
unsigned int val = 0, bs = 1;
for (int i = 1; i <= n; i++) {
val += p[i]*bs;
bs *= (n+1);
}
return val%3214567;
}
// ------
int val(int pp[MAXN]) // 计算某一置换下不变的方案数, dp
{
int k = hash_p(pp);
if (hsh[k] == 1) return 0;
else hsh[k] = 1, cnp++;
memset(fa, 0, sizeof fa);
for (int i = 1; i <= n; i++) siz[i] = 1;
for (int i = 1; i <= n; i++) link(i, pp[i]);
gt = 0;
for (int i = 1; i <= n; i++)
if (fa[i] == 0)
ht[++gt] = siz[i];
memset(dp, 0, sizeof dp);
dp[0][0][0][0] = 1;
for (int i = 1; i <= gt; i++)
for (int j = 0; j <= Sr; j++)
for (int k = 0; k <= Sg; k++)
for (int l = 0; l <= Sb; l++) {
if (j>=ht[i]) (dp[i][j][k][l] += dp[i-1][j-ht[i]][k][l]) %= p;
if (k>=ht[i]) (dp[i][j][k][l] += dp[i-1][j][k-ht[i]][l]) %= p;
if (l>=ht[i]) (dp[i][j][k][l] += dp[i-1][j][k][l-ht[i]]) %= p;
}
// cout << dp[gt][Sr][Sg][Sb] << endl;
return dp[gt][Sr][Sg][Sb];
}
int main()
{
scanf("%d%d%d%d%d", &Sr, &Sg, &Sb, &m, &p);
n = Sr + Sg + Sb;
int ans = 0;
for (int i = 1; i <= n; i++) per[i] = i; // 加个幺元
(ans += val(per)) %= p;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) scanf("%d", &per[j]);
(ans += val(per)) %= p;
}
cout << ans*inv(cnp)%p << endl;
return 0;
}
1007: [HNOI2008]水平可见直线
水水的半平面交?都是大于号,用一个栈处理就好了,甚至不需要计算几何知识就能脑补1A…五道题打卡下班。
#include <bits/stdc++.h>
using namespace std;
struct hp {
double a, b;
int id;
hp(){}
hp(double x, double y, int i):a(x),b(y),id(i){}
friend bool operator < (const hp &a, const hp &b)
{ return a.a==b.a?a.b>b.b:a.a < b.a; }
} sg[50005];
int n;
stack<int> stk;
int ptw[50005], tp = 0;
bool onleft(const hp &a, const hp &b, const hp &sd)
{
double x = (b.b-a.b)/(a.a-b.a), y = a.a*x+a.b;
return y+1e-7 >= sd.a*x+sd.b;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
double a, b;
scanf("%lf%lf", &a, &b);
sg[i] = hp(a, b, i);
}
sort(sg+1, sg+n+1);
int nw = n;
for (int i = 1; i <= n; i++)
if (sg[i].a == sg[i-1].a) {
sg[i].a = INT_MAX;
nw--;
}
sort(sg+1, sg+n+1);
n = nw;
for (int i = 1; i <= n; i++) {
// cout << sg[i].id << endl;
while (stk.size() >= 2) {
int tp = stk.top(); stk.pop();
int t2 = stk.top(); stk.pop();
if (onleft(sg[i], sg[t2], sg[tp])) stk.push(t2);
else {stk.push(t2), stk.push(tp); break;}
}
stk.push(i);
}
while (!stk.empty()) ptw[++tp] = sg[stk.top()].id, stk.pop();
sort(ptw+1, ptw+tp+1);
for (int i = 1; i <= tp; i++)
printf("%d ", ptw[i]);
return 0;
}
bzoj1015: [JSOI2008]星球大战starwar
删除难而加入容易,只要反过来做就好了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 400005, MAXM = 200005;
struct edge {
int s, t;
} graph[MAXM];
struct node {
int to, next;
} edge_list[2*MAXM];
int head[MAXN], top = 0;
inline void push(int i, int j)
{ ++top, edge_list[top] = (node) {j, head[i]}, head[i] = top; }
int n, m;
int q[MAXN], l = 0;
int fa[MAXN];
int vis[MAXN], ans[MAXN], cnt, del = 0; // cnt : 联通块个数
inline int findf(int i)
{ return fa[i]!=-1?fa[i]=findf(fa[i]):i; }
void link(int i, int j)
{
int p = findf(i), q = findf(j);
if (p != q) {
fa[p] = q;
cnt--;
}
}
int main()
{
memset(fa, -1, sizeof fa);
memset(head, -1, sizeof head);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &graph[i].s, &graph[i].t);
push(graph[i].s, graph[i].t), push(graph[i].t, graph[i].s);
}
scanf("%d", &l);
for (int i = 1; i <= l; i++) scanf("%d", &q[i]), vis[q[i]] = i;
// for (int i = 1; i <= l; i++) cout << vis[q[i]] << " "; puts("");
cnt = n;
for (int i = 1; i <= l; i++) del += vis[q[i]] == i;
for (int i = 1; i <= m; i++)
if (!vis[graph[i].s] && !vis[graph[i].t])
link(graph[i].s, graph[i].t);
for (int i = l; i >= 1; i--) {
ans[i] = cnt-del;
// cout << del << endl;
if (vis[q[i]] == i) {
del--;
vis[q[i]] = 0;
for (int j = head[q[i]]; j != -1; j = edge_list[j].next) {
int to = edge_list[j].to;
if (vis[to] == 0)
link(q[i], to);
}
}
}
cout << cnt << endl;
for (int i = 1; i <= l; i++)
printf("%d\n", ans[i]);
return 0;
}
bzoj2242: [SDOI2011]计算器
数论模板题,复习bsgs。
注意bsgs的第二步求逆要在循环外求,否则会被肉眼可见卡常数。
#include <bits/stdc++.h>
using namespace std;
long long y, z, p;
int t, k;
long long power(long long a, long long n)
{
if (a == 0) return 0;
if (n == 0) return 1;
long long k = power(a, n>>1);
(k *= k) %= p;
if (n&1) (k *= a) %= p;
return k;
}
long long inv(long long a)
{ return power(a, p-2); }
map<long long, long long> hsh;
void work()
{
if (k == 1) {
printf("%lld\n", power(y, z));
} else if (k == 2) {
long long x = inv(y)*z%p;
if (x > 0 && x*y%p == z%p) printf("%lld\n", x);
else puts("Orz, I cannot find x!");
} else {
y %= p, z %= p;
if (y == 0 && z == 0) {puts("1");return;}
if (y == 0) {puts("Orz, I cannot find x!"); return;}
hsh.clear();
long long tp = ceil(sqrt(p)+1e-7);
long long delta = 1;
for (int i = 1; i <= tp; i++) {
(delta *= y) %= p;
if (!hsh.count(delta))hsh[delta] = i;
}
long long bs = 1;
bool fd = 0;
delta = inv(delta);
for (int i = 0; i*tp <= p; i++) {
if (hsh.count(bs*z%p)) {
// cout << i*tp << " " << bs << " " << inv(bs) << " " << hsh[inv(bs)] << endl;
printf("%lld\n", i*tp+hsh[bs*z%p]);
fd = 1;
break;
}
(bs *= delta) %= p;
}
if (!fd) puts("Orz, I cannot find x!");
}
}
int main()
{
scanf("%d%d", &t, &k);
for (int i = 1; i <= t; i++) {
scanf("%lld%lld%lld", &y, &z, &p);
work();
}
return 0;
}
bzoj1014: [JSOI2008]火星人prefix
一遇数据结构就跪综合症…因为位置和树上位置的转化调了一个多小时。
首先“插入不超过10000个”可以想到是
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 2000005;
int chl[MAXM][2];
unsigned long long dat[MAXM], cha[MAXM], power[MAXM];
unsigned int siz[MAXM], fa[MAXM];
int top = 0, root = 0;
inline int new_node(char c, int f)
{ return ++top, dat[top] = cha[top] = c-'a'+1, siz[top] = 1, fa[top] = f, top; }
void update(int nd)
{
siz[nd] = siz[chl[nd][0]] + siz[chl[nd][1]] + 1;
dat[nd] = dat[chl[nd][0]] + power[siz[chl[nd][0]]]*(cha[nd] + (unsigned long long)27*dat[chl[nd][1]]);
}
void zig(int nd)
{
// cout << "zigging : " << nd << endl;
int p = fa[nd], g = fa[fa[nd]], tp = chl[p][0] != nd;
int tg = chl[g][0] != p, son = chl[nd][tp^1];
if (son) fa[son] = p; fa[p] = nd, fa[nd] = g;
if (g) chl[g][tg] = nd;
else root = nd;
chl[nd][tp^1] = p, chl[p][tp] = son;
update(p), update(nd);
}
void dfs(int nd, int tab = 0)
{
if (!nd) return;
for (int i = 0; i < tab; i++) cout << " ";
printf("%d--%c--(%d,%d), siz = %d, fa = %d\n", nd, cha[nd], chl[nd][0], chl[nd][1], siz[nd], fa[nd]);
dfs(chl[nd][0], tab+2);
dfs(chl[nd][1], tab+2);
}
void splay(int nd, int tar = 0)
{
// cout << "splaying : " << nd << " --> son of " << tar << endl;
while (fa[nd] != tar) {
// cout << nd << endl;
int p = fa[nd], g = fa[nd], tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (fa[p] == tar) {zig(nd); break;}
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
} // splay tree
int position(int k)
{
if (k > siz[root] || k <= 0) return 0;
k--;
int nd = root;
while (k != siz[chl[nd][0]]) {
// cout << nd << " " << k << endl;
nd = (siz[chl[nd][0]] > k) ? (chl[nd][0]) : (k -= siz[chl[nd][0]]+1, chl[nd][1]);
}
return nd;
} // get the position of k-th element
int segment(int l, int r)
{
int k = position(l-1), p = position(r+1);
if (l == 1 && r == siz[root]) return root;
if (l == 1) return splay(p), chl[root][0];
if (r == siz[root]) return splay(k), chl[root][1];
return splay(k), splay(p, root), chl[chl[root][1]][0];
} // get a segment [l, r]
unsigned long long query(int l, int r)
{ return dat[segment(l, r)]; } // get the hash value in [l, r]
void insert(int k, char c) // push a new element after k-th element
{
// cout << k << " " << c << " " << siz[root] << endl;
if (!root && k == 0) {
root = new_node(c, 0);
return;
} // no element
if (k == 0) {
splay(position(1));
chl[root][0] = new_node(c, root);
update(root);
} else {
int nd = segment(k, k);
// puts("-----"); dfs(root);
chl[nd][1] = new_node(c, nd);
while (nd)
update(nd), nd = fa[nd];
}
// dfs(root);
}
void change(int k, char c)
{ splay(k), cha[k] = c-'a'+1, update(k);
} // change the value of an existed element
void init()
{
power[0] = 1;
for (int i = 1; i < MAXM; i++)
power[i] = power[i-1]*27;
// power for hash
}
// -------------
int ask(int opl, int opr)
{
int len = siz[root]; // maxlen
int l = 0, r = len-max(opl, opr)+1;
while (l <= r) {
int mid = (l+r)>>1;
if (query(opl, opl+mid-1) == query(opr, opr+mid-1)) l = mid+1;
else r = mid-1;
}
return l-1;
}
int n;
char str[MAXM];
char get_char()
{
char c;
do c = getchar(); while(!isalpha(c));
return c;
}
inline int read() {
int a = 0, c;
do c = getchar(); while(!isdigit(c));
while (isdigit(c)) {
a = a*10+c-'0';
c = getchar();
}
return a;
}
int main()
{
init();
scanf("%s", str);
for (int i = 0, l = strlen(str); i < l; i++)
insert(i, str[i]); // build a tree
n = read();
int cnt = 0;
for (int i = 1; i <= n; i++) {
char c = get_char();
int l, r, p; char d;
switch (c) {
case 'Q':
cnt++;
l = read(), r = read();
printf("%d\n", ask(l, r));
break;
case 'R':
p = read(), d = get_char();
if (!isalpha(d)) throw;
change(position(p), d);
break;
case 'I':
p = read(), d = get_char();
if (!isalpha(d)) throw;
insert(p, d);
break;
default:
throw;
break;
}
}
return 0;
}