继续上HNOI 2009的题解。
由于数据范围为100000,暴力肯定是不行的。(好像是废话…)
我们目前存在两个问题:
介绍一下启发式合并,通俗的说就是把小的合并到大的块上去。
合并的时间是 O(min{len1,len2}) ,而有效合并的次数不超过 O(logn) ,所以时间复杂度为 O(nlogn) 。
而我们只需要在合并的时候(假设 a→b )判断该 a 颜色的两边是否有 b 颜色,若有答案相减即可。
其实我们还漏了一个小问题,万一我们需要将 a 颜色变为 b 颜色,而 a 颜色却大于 b 颜色,难道我们要将大的合并至小的上吗?那样时间复杂度就不合要求了。
其实我们只需要令 f[x] 表示颜色 x 在块中的颜色,需要时交换f[x]即可,这样我们就依然能保证从小的合并到大的上。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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=1|G|∑mc(gi)
其中 L 为等价类个数, |G| 为置换个数, m 为颜色种数, c(gi) 表示第 i 个置换的不动点数(也就是循环个数)。
根据题意,点置换有 N! 个,很明显对于 N=60 的数据是不可能一一计算循环个数的,但我们可以发现有很多种置换的循环是等价的,也就是说我们可以只求出其中很小一部分的答案就可以了。
我们考虑将点置换映射到边置换上(==,点置换无法计算颜色啊,边置换可以用两种颜色表示有还是没有这条边),每个点置换是唯一对应一个边置换的。
即点置换: i→p[i] ,边置换: (i,j)→(p[i],p[j]) 。
考虑对于 n ,我们将其划分为 m 段,且有:
L1≥L2≥L3≥ ...≥Lm>0 ,n=∑Li 。
将每一段视为一个循环,我们考虑在此种点置换下边置换的循环个数。
考虑一条边 (i,j)
1. 若 i,j 在同一循环 L 内,则此种边构成的循环个数为 [L2] 个。
证明:
1o 当 L 为奇数时,我们考虑一个循环节 (1,2,3,...,2n+1),L=2n+1 ,边置换为 [(1,2)(2,3)(1,3)(2,4)(1,4)(2,5)......(2n,2n+1)(2n+1,1)]
对于以边 (1,k),k≤n 开头的循环,我们可以经过 2n+1 步走过一个循环,又一共有 n(2n+1) 个置换,故有 n=[L2] 个循环。
2o 当 L 为偶数时,类似的当 k≤L2−1 的时候与上面类似,只是当 k=L2 我们只需经 L2 步即可走完循环。
综上,证毕。
2. 若 i,j 在两循环 L1,L2 内,则此种边构成的循环个数为 gcd(L1,L2) 个。
证明:
两循环间共有 L1L2 条边,而类似上面证明的我们考虑只有走了 lcm(L1,L2) 步我们才能回到原点,因此共有 L1L2lcm(L1,L2)=gcd(L1,L2) 个循环。
有了以上结论,我们可以得出对于 n 的一个划分的循环个数为关于 L1,L2,...,Lm 的函数。
接下来我们考虑有多少种点置换对应了这种划分。
考虑将 N 个点插入有 N! 种方法,而每个循环内重复计算了 Li 次,并且当存在 Li=Li+1=...=Lj 的时候,它们的顺序时刻任意调换的,故总的个数为:
N!L1L2...Lm∗k1k2...kt
其中 t 表示有 t 种不同的 L 值,每种值有 ki 个。
我们只需把每种划分对应的置换个数 ∗2c 统计入答案,其中 c 为边置换的循环节数,即可。
至于每种划分我们dfs即可。
对于这种置换总数非常大的题目,我们可以从不同的循环下手,计算每种循环对应了多少置换来简化计算。
AC code:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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] 表示第 i 个元素所在的序列有 j 个元素时,另一个序列最后一个元素的最小值, dp 方程为:
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
不会做…先晾在这吧…