A. Monotonic Matrix
给出n和m,问存在多少个n*m矩阵使得对于任意i, j都满足a(i, j) <= a(i+1, j)且a(i, j) <= a(i, j+1),其中0 <= a(i, j) <= 2。
显然一个位置的元素不大于右下方三个位置的元素。考虑0和1、1和2的分界线,题目就变成了:从(n, 0) 到(0, m)的两条不相交可重合路线有多少种方案。
考虑将其中一条路径平移,就转化为不可重叠的方案数。计算从(0, 0)到(n, m)的方案数时,考虑一条从(1, 0)到(n, m-1),另一条从(0, 1)到(n-1, m)。二者乘起来再减去相交的,即减去从(0, 1)到(n, m-1)与(1, 0)到(n-1, m)的方案数。
实际上就是套Lindström–Gessel–Viennot定理,答案为。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 2050;
const ll mod = 1e9 + 7;
int n, m;
ll C[maxn][maxn];
void init()
{
for(int i = 0;i <= 2000;i++) C[i][i] = C[i][0] = 1;
for(int i = 2;i <= 2000;i++)
{
for(int j = 1;j < i;j++)
C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
}
}
int main()
{
init();
while(~scanf("%d%d", &n, &m))
{
ll ans = ((C[n+m][m]*C[n+m][m] % mod) - (C[n+m][m-1]*C[n+m][n-1] % mod) + mod) % mod;
printf("%lld\n", ans);
}
}
D. Two-Graghs
给出两个图G1、G2,问G2的子图中有多少个与G1同构。
枚举所有与G1同构的图,然后判断是否是G2的子图即可。在判断的时候,由于最多只有28条边,可以用位运算压到一个int里面来记录边的组合方案是否重复。总的时间复杂度为。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 12;
int n, ma, mb, u, v, a[maxn];
int ga[maxn][maxn], gb[maxn][maxn];
map<int, int>mp;
int main()
{
while(scanf("%d%d%d", &n, &ma, &mb) != EOF)
{
mp.clear();
memset(ga, 0, sizeof(ga));
memset(gb, 0, sizeof(gb));
for(int i = 1;i <= ma;i++)
{
scanf("%d%d", &u, &v);
ga[u][v] = ga[v][u] = 1;
}
for(int i = 1;i <= mb;i++)
{
scanf("%d%d", &u, &v);
gb[u][v] = gb[v][u] = i;
}
for(int i = 1;i <= n;i++) a[i] = i;
int ans = 0;
do
{
int flag = 1, tmp = 0;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(!ga[i][j]) continue;
if(!gb[a[i]][a[j]]) {flag = 0; break;}
tmp |= (1 << gb[a[i]][a[j]]);
}
if(!flag) break;
}
if(flag && !mp[tmp]) {mp[tmp] = 1; ans++;}
}while(next_permutation(a+1, a+n+1));
printf("%d\n", ans);
}
return 0;
}
E. Removal
给出一个长度为n的数组,要求去掉m个元素,问能得到多少个不同的新数组。
实际上就是问长度为n-m的不同的子序列有多少。考虑先预处理出nxt[i][x]表示在位置i,数字x下一次出现的位置。用dp[i][j]表示前i个位置去掉了j个的方案数,在每一个位置上都枚举去掉的数目与处理的下个位置,显然有:
dp[nxt(i, p)][nxt(i, p)+j-i-1] += dp[i][j]。
最后的答案是。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 100050;
const ll mod = 1e9 + 7;
int n, m, k, a[maxn];
int nxt[maxn][12];
ll dp[maxn][12];
int main()
{
while(~scanf("%d%d%d", &n, &m, &k))
{
for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
for(int i = 1;i <= k;i++) nxt[n][i] = n + 1;
for(int i = n;i >= 1;i--)
{
for(int j = 1;j <= k;j++)
nxt[i-1][j] = nxt[i][j];
nxt[i-1][a[i]] = i;
}
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i = 0;i < n;i++)
{
for(int j = 0;j <= m;j++)
{
for(int p = 1;p <= k;p++)
{
int tmp = nxt[i][p], tp = j+tmp-i-1;
if(tp <= m)
dp[tmp][tp] = (dp[tmp][tp] + dp[i][j]) % mod;
}
}
}
ll ans = 0;
for(int i = 0;i <= m;i++)
ans = (ans + dp[n-i][m-i] + mod) % mod;
printf("%lld\n", ans);
}
return 0;
}
J. Different Integers
给出一个数列,要求q次查询,每次给出l, r,求[1, l] 和 [r, n]中不同元素的个数。
首先考虑如何求一个区间[l, r]中不同元素的个数。这个东西可以用树状数组来维护,从前往后遍历这个数列,对于一个元素a[i],在i这个位置上加1,同时如果a[i]在前面出现过,那么就在上一次出现的位置上减1。出现的不同元素个数就是维护的这个树状数组的区间和。
需要注意的是树状数组的这个解法需要将所有查询按照右端点排序然后离线解决。
而这个问题中,[1, l]和[r, n]区间上的答案实际上就是[r, n+l]上的答案,只需要将原数列倍增一遍即可。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 200050;
int n, q, a[maxn], vis[maxn];
int tree[maxn], ans[maxn];
struct node
{
int l, r;
int id;
bool operator < (const node &o) const
{
return r < o.r;
}
}e[maxn];
int lowbit(int x) {return x & (-x);}
void add(int x, int w)
{
while(x < maxn)
{
tree[x] += w;
x += lowbit(x);
}
}
int query(int x)
{
int res = 0;
while(x)
{
res += tree[x];
x -= lowbit(x);
}
return res;
}
int main()
{
while(scanf("%d%d", &n, &q) != EOF)
{
for(int i = 1;i <= n;i++)
{
scanf("%d", &a[i]);
a[i+n] = a[i];
}
memset(tree, 0, sizeof(tree));
memset(vis, 0, sizeof(vis));
for(int i = 1;i <= q;i++)
{
scanf("%d%d", &e[i].l, &e[i].r);
e[i].l += n;
swap(e[i].l, e[i].r);
e[i].id = i;
}
sort(e+1, e+q+1);
int pos = 1;
for(int i = 1;i <= n*2;i++)
{
if(vis[a[i]]) add(vis[a[i]], -1);
vis[a[i]] = i;
add(vis[a[i]], 1);
while(e[pos].r <= i && pos <= q)
{
ans[e[pos].id] = query(e[pos].r) - query(e[pos].l - 1);
pos++;
}
}
for(int i = 1;i <= q;i++)
printf("%d\n", ans[i]);
}
return 0;
}