继续上HNOI 2009的题解。
梦幻布丁
由于数据范围为100000,暴力肯定是不行的。(好像是废话…)
我们目前存在两个问题:
1.合并两个不同的颜色;
2.输出块数。
介绍一下启发式合并,通俗的说就是把小的合并到大的块上去。
合并的时间是
O(min{len1,len2})
,而有效合并的次数不超过
O(logn)
,所以时间复杂度为
O(nlogn)
。
而我们只需要在合并的时候(假设
a→b
)判断该
a
颜色的两边是否有
其实我们还漏了一个小问题,万一我们需要将
a
颜色变为
其实我们只需要令 f[x] 表示颜色 x 在块中的颜色,需要时交换f[x]即可,这样我们就依然能保证从小的合并到大的上。
3.set水过版
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int maxn = 100009;
const int maxc = 1000009;
int n, m;
int a[maxn];
int f[maxc];
set<int> C[maxc];
int ans;
void solve(int u, int v)
{
for(set<int>::iterator it = C[u].begin(); it != C[u].end(); ++it)
{
if(a[*it-1] == v) ans--;
if(a[*it+1] == v) ans--;
C[v].insert(*it);
}
for(set<int>::iterator it = C[u].begin(); it != C[u].end(); ++it)
a[*it] = v;
C[u].clear();
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i < maxc; ++i) f[i] = i;
for(int i = 1; i <= n; ++i)
{
scanf("%d", a+i);
if(a[i] != a[i-1]) ans++;
C[a[i]].insert(i);
}
for(int i = 1; i <= m; ++i)
{
int T;scanf("%d", &T);
if(T == 2) printf("%d\n", ans);
else
{
int u, v;
scanf("%d%d", &u, &v);
if(u == v) continue;
if(C[f[u]].size() > C[f[v]].size()) swap(f[u], f[v]);
solve(f[u], f[v]);
}
}
return 0;
}
4.链表版
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int maxn = 100009;
const int maxc = 1000009;
int n, m;
int a[maxn];
int f[maxc];
list<int> C[maxc];
int ans;
void solve(int u, int v)
{
for(list<int>::iterator i = C[u].begin(); i != C[u].end(); ++i)
{
if(a[*i-1] == v) ans--;
if(a[*i+1] == v) ans--;
C[v].push_back(*i);
}
for(list<int>::iterator i = C[u].begin(); i != C[u].end(); ++i)
a[*i] = v;
C[u].clear();
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i < maxc; ++i) f[i] = i;
for(int i = 1; i <= n; ++i)
{
scanf("%d", a+i);
if(a[i] != a[i-1]) ans++;
C[a[i]].push_back(i);
}
for(int i = 1; i <= m; ++i)
{
int T;scanf("%d", &T);
if(T == 2) printf("%d\n", ans);
else
{
int u, v;
scanf("%d%d", &u, &v);
if(u == v) continue;
if(C[f[u]].size() > C[f[v]].size()) swap(f[u], f[v]);
solve(f[u], f[v]);
}
}
return 0;
}
图的同构
很明显是Pólya计数,但怎样推理才是最重要的。
首先,我们来回忆一下Pólya计数的公式:
其中
L
为等价类个数,
根据题意,点置换有
我们考虑将点置换映射到边置换上(==,点置换无法计算颜色啊,边置换可以用两种颜色表示有还是没有这条边),每个点置换是唯一对应一个边置换的。
即点置换:
i→p[i]
,边置换:
(i,j)→(p[i],p[j])
。
考虑对于
n
,我们将其划分为
L1≥L2≥L3≥ ...≥Lm>0 ,n=∑Li
。
将每一段视为一个循环,我们考虑在此种点置换下边置换的循环个数。
考虑一条边
(i,j)
1.
若
i,j
在同一循环
L
内,则此种边构成的循环个数为
证明:
1o
当
L
为奇数时,我们考虑一个循环节
对于以边
(1,k),k≤n
开头的循环,我们可以经过
2n+1
步走过一个循环,又一共有
n(2n+1)
个置换,故有
n=[L2]
个循环。
2o
当
L
为偶数时,类似的当
综上,证毕。
2.
若
i,j
在两循环
L1,L2
内,则此种边构成的循环个数为
gcd(L1,L2)
个。
证明:
两循环间共有
L1L2
条边,而类似上面证明的我们考虑只有走了
lcm(L1,L2)
步我们才能回到原点,因此共有
L1L2lcm(L1,L2)=gcd(L1,L2)
个循环。
有了以上结论,我们可以得出对于
n
的一个划分的循环个数为关于
接下来我们考虑有多少种点置换对应了这种划分。
考虑将
N
个点插入有
N!L1L2...Lm∗k1k2...kt
其中
t
表示有
我们只需把每种划分对应的置换个数 ∗2c 统计入答案,其中 c 为边置换的循环节数,即可。
至于每种划分我们dfs即可。
对于这种置换总数非常大的题目,我们可以从不同的循环下手,计算每种循环对应了多少置换来简化计算。
AC code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <stack>
typedef long long LL;
typedef long double LD;
typedef double DB;
using namespace std;
const int MaxN = 69;
const int Mod = 997;
int n;
int inv[MaxN];
int L[MaxN];
int ans;
int Gcd[MaxN][MaxN];
int invl[MaxN];
int mi[MaxN*MaxN];
int PowerMod(int a, int b, int m)
{
int re = 1, base = a;
while(b)
{
if(b&1) re = re*base%m;
base = base*base%m;
b >>= 1;
}
return re;
}
#define Inv(a, b) (PowerMod(a, b-2, b))
int gcd(int a, int b)
{
if(b) return gcd(b, a%b);
else return a;
}
int calc(int m)
{
int re = 1;
for(int i = 1, l = 1; i <= m; ++i)
{
re = re*invl[L[i]]%Mod;
if(i == m || L[i] != L[i+1])
{
re = re*inv[l]%Mod;
l = 1;
}
else l++;
}
int num = 0;
for(int i = 1; i <= m; ++i)
{
num += L[i]/2;
for(int j = i+1; j <= m; ++j)
num += Gcd[L[i]][L[j]];
}
re = re*mi[num]%Mod;
return re;
}
void Dfs(int cnt, int sum)
{
L[cnt] = n-sum;
ans = (ans+calc(cnt))%Mod;
for(int i = L[cnt-1]; i*2 <= n-sum; ++i)
{
L[cnt] = i;
Dfs(cnt+1, sum+i);
}
}
int main()
{
cin >> n;
inv[0] = 1;
for(int i = 1; i <= n; ++i)
{
inv[i] = inv[i-1]*Inv(i, Mod)%Mod;
invl[i] = Inv(i, Mod);
}
mi[0] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
Gcd[i][j] = gcd(i, j), mi[(i-1)*n+j] = mi[(i-1)*n+j-1]*2%Mod;
L[0] = 1;
Dfs(1, 0);
cout << ans << endl;
return 0;
}
双递增序
设
f[i][j]=min{f[i−1][j−1](s[i]>s[i−1]), s[i−1](s[i]>f[i−1][i−j])}
AC code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <stack>
typedef long long LL;
typedef long double LD;
typedef double DB;
using namespace std;
const int MaxN = 2009;
const int inf = 0x3f3f3f3f;
int n, s[MaxN];
int f[MaxN][MaxN<<1];
int main()
{
int Test;scanf("%d", &Test);
while(Test--)
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", s+i);
memset(f, inf, sizeof(f));
f[0][0] = 0;
for(int i = 0; i < n; ++i)
for(int j = 0; j <= (n>>1) && j <= i; ++j)
if(f[i][j] < inf)
{
if(s[i+1] > s[i]) f[i+1][j+1] = min(f[i+1][j+1], f[i][j]);
if(s[i+1] > f[i][j]) f[i+1][i-j+1] = min(f[i+1][i-j+1], s[i]);
}
if(f[n][n>>1] < inf) puts("Yes!");
else puts("No!");
}
return 0;
}
通往城堡之路
题解传送门(其实是我个地方没看懂= =):
http://blog.csdn.net/ts124124/article/details/6249475
AC code:
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int maxn = 5009;
const LL inf = 1LL<<62;
int n;
LL d;
LL h[maxn];
LL b[maxn];
LL ans;
int main()
{
int TAT;
scanf("%d", &TAT);
while(TAT--)
{
scanf("%d%lld", &n, &d);
for(int i = 1; i <= n; ++i)
scanf("%lld", h+i);
if(h[1]-(n-1)*d > h[n] || h[1]+(n-1)*d < h[n]) puts("impossible");
else
{
b[1] = h[1];
for(int i = 2; i <= n; ++i)
b[i] = b[i-1]-d;
while(h[n] != b[n])
{
int now = 0, j = 0;
LL up = inf, Max = -inf, val = 0;
for(int i = n; i >= 2; --i)
{
if(b[i] >= h[i]) now--;
else now++, up = min(up, h[i]-b[i]);
if(now > Max && b[i] < b[i-1]+d)
{
Max = now;
val = min(up, b[i-1]+d-b[i]);
j = i;
}
}
for(int i = j; i <= n; ++i)
b[i] += val;
}
ans = 0;
for(int i = 1; i <= n; ++i)
ans += abs(b[i]-h[i]);
printf("%lld\n", ans);
}
}
return 0;
}
无归岛
比较水的仙人掌 dp ,因为只有题目中只有环,所以搜到环时,直接做两遍 dp , f[]、g[] 分别表示取或不取这个点,做两遍 dp ,注意初始化即可。
AC code:
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int MaxN = 100009;
const int MaxM = 200009;
const int inf = 2e9+5;
int n, m;
struct UGraph
{
int size;
int head[MaxN];
int to[MaxM<<1];
int ne[MaxM<<1];
UGraph(){size = 1;}
inline void AddEdge(int u, int v)
{
to[size] = v, ne[size] = head[u], head[u] = size++;
to[size] = u, ne[size] = head[v], head[v] = size++;
}
}G;
int _Time_;
int dfn[MaxN];
int f[MaxN], g[MaxN];
int fa[MaxN];
int u0, v0;
int u1, v1;
void update(int start, int end)
{
u0 = v0 = 0;
for(int i = end; i != start; i = fa[i])
{
u1 = f[i]+v0;
v1 = g[i]+max(u0, v0);
u0 = u1, v0 = v1;
}
g[start] += max(u0, v0);
v0 = -inf, u0 = 0;
for(int i = end; i != start; i = fa[i])
{
u1 = f[i]+v0;
v1 = g[i]+max(u0, v0);
u0 = u1, v0 = v1;
}
f[start] += v0;
}
void dfs(int cnt, int father)
{
dfn[cnt] = ++_Time_;
fa[cnt] = father;
for(int i = G.head[cnt]; i; i = G.ne[i])
if(!dfn[G.to[i]])
dfs(G.to[i], cnt);
for(int i = G.head[cnt]; i; i = G.ne[i])
if(fa[G.to[i]] != cnt && dfn[G.to[i]] > dfn[cnt])
update(cnt, G.to[i]);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i)
{
static int u, v;
scanf("%d%d", &u, &v);
G.AddEdge(u, v);
}
for(int i = 1; i <= n; ++i)
scanf("%d", f+i);
dfs(1, 0);
printf("%d\n", max(f[1], g[1]));
return 0;
}
有趣的数列
打表找规律后我们可以发现是 Catalan 数,然而由于模数不是质数,且一个一个数的质因数分解会超时,我们可以用欧拉筛法在筛素数的过程中记录其最小质因子,在乘法时对该质因子++,除法时- -,直接分解即可。
AC code:
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int MaxN = 1000009;
int n, p;
int prime[MaxN<<1], tot;
int Min[MaxN<<1];
int s[MaxN<<1];
LL ans = 1;
void Euler(int n)
{
static int hash[MaxN<<1] = {0};
for(int i = 2; i <= n; ++i)
{
if(!hash[i]) prime[++tot] = i, Min[i] = tot;
for(int j = 1; j <= tot && prime[j]*i <= n; ++j)
{
hash[prime[j]*i] = 1;Min[prime[j]*i] = j;
if(i%prime[j] == 0) break;
}
}
}
void Add(int x, int f)
{
while(x != 1)
{
s[Min[x]] += f;
x /= prime[Min[x]];
}
}
int main()
{
cin >> n >> p;
Euler(2*n);
for(int i = 2*n; i > n; --i) Add(i, 1);
for(int i = n; i >= 2; --i) Add(i, -1);
Add(n+1, -1);
for(int i = 1; i <= tot; ++i)
while(s[i]--) ans = (ans*prime[i])%p;
cout << ans << endl;
return 0;
}
最小圈
二分答案,然后 spfa 判负环即可,有个神奇的地方是我们要用 dfs 的 spfa 才会不超时。
AC code:
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <queue>
typedef long long LL;
typedef double DB;
typedef long double LD;
using namespace std;
const int MaxN = 3009;
const int MaxM = 10009;
const double inf = 1e8;
const double eps = 1e-9;
int n, m;
struct DGraph
{
int Es;
int head[MaxN];
int to[MaxM];
int ne[MaxM];
DB d[MaxM];
DGraph()
{
Es = 1;
memset(head, 0, sizeof(head));
}
inline void AddEdge(int u, int v, DB w)
{
to[Es] = v;d[Es] = w;ne[Es] = head[u];head[u] = Es++;
}
inline void AddVal(DB k)
{
for(int i = 1; i < Es; ++i)
d[i] += k;
}
}G;
bool InStack[MaxN];
DB dis[MaxN];
bool spfa(int u)
{
bool flag = false;
InStack[u] = true;
for(int i = G.head[u]; i; i = G.ne[i])
{
if(dis[G.to[i]] > G.d[i]+dis[u])
{
if(InStack[G.to[i]]) {flag = true;break;}
else
{
dis[G.to[i]] = dis[u]+G.d[i];
if(spfa(G.to[i])) {flag = true;break;}
}
}
}
InStack[u] = false;
return flag;
}
bool check(DB mid)
{
bool flag = false;
G.AddVal(-mid);
memset(dis+1, 0.0, n*sizeof(DB));
for(int i = 1; i <= n; ++i)
if(spfa(i)) {flag = true;break;}
G.AddVal(mid);
return flag;
}
int main()
{
scanf("%d%d", &n, &m);
DB l = inf, r = 0;
for(int i = 1; i <= m; ++i)
{
int u, v, d;
scanf("%d%d%d", &u, &v, &d);
G.AddEdge(u, v, d*1.0);
l = min(l, d*1.0);r = max(r, d*1.0);
}
while(r-l > eps)
{
DB mid = (l+r)*0.5;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.8lf\n", l);
return 0;
}
积木 Block
不会做…先晾在这吧…