有史以来爆的最惨的一次,这个故事告诉我们一定要对拍!!!特别是自己没有把握的题目!!!
T1 bricks
Solution
直接三分即可,有平台的话就说明是答案了。
但是考试写的贪心不知道为什么挂了,似乎mys学长跟我写的思路一样但是他过了,我保龄了。。。
T2 二分图染色
Description
给定一个完全二分图,图的左右两边的顶点数目相同。我们要把图中的每条边染成红色、蓝色、或者绿色,并使得任意两条红边不共享端点、同时任意两条蓝边也不共享端点。计算所有满足条件的染色的方案数,并对 (10^{9}+7) 取模。
(nle 10^7)
Solution
考试的时候推出来的式子似乎并没有办法优化到 (Theta(n)),大概长成这个样子:
有优化方法的大佬可以帮一下我这个小蒟蒻。
我们可以发现,这个问题实际上就是在一个 (n imes n) 的棋盘,放“车”,使得不同颜色的车不攻击,且一个点只能放一个,问合法方案数。
不难发现在只有一种颜色的情况下,答案就是:
其中 (f(n)) 表示棋盘大小为 (n) 的方案数。
但是现在有两种颜色,所以我们需要考虑容斥。
我们可以枚举有多少个点是相同的,那么答案就是:
于是问题就是如何求出 (f(x))。不难想到递推。
对于新增加的一行一列有 (2n) 种选法(选 (2n-1) 个格子以及不选),但是你发现这样会算重,因为可能选到行和列上的点,两者不影响就会算重。所以你可以枚举这个两个点覆盖范围的交点,所以可以得到递推式
:
时间复杂度 (Theta(n))。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define mod 1000000007
#define MAXN 10000005
//char buf[1<<21],*p1=buf,*p2=buf;
//#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,ans,f[MAXN],fac[MAXN],ifac[MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
int qkpow (int a,int b){int res = 1;for (;b;b >>= 1,a = mul (a,a)) if (b & 1) res = mul (res,a);return res;}
int binom (int a,int b){return a >= b ? mul (fac[a],mul (ifac[b],ifac[a - b])) : 0;}
int sqr (int x){return mul (x,x);}
signed main(){
read (n);
fac[0] = 1;for (Int i = 1;i <= n;++ i) fac[i] = mul (fac[i - 1],i);
ifac[n] = qkpow (fac[n],mod - 2);for (Int i = n;i;-- i) ifac[i - 1] = mul (ifac[i],i);
f[0] = 1,f[1] = 2;for (Int i = 2;i <= n;++ i) f[i] = dec (mul (2 * i,f[i - 1]),mul (sqr (i - 1),f[i - 2]));
for (Int i = 0,tmp;i <= n;++ i)
tmp = mul (sqr (f[n - i]),mul (fac[i],sqr (binom (n,i)))),
ans = i & 1 ? dec (ans,tmp) : add (ans,tmp);
write (ans),putchar ('
');
return 0;
}
T3 [eJOI2018]循环排序
Description
给定一个长为 (n) 的数列 ({a_i}) ,你可以多次进行如下操作:
选定 (k) 个不同的下标 (i_1, i_2, cdots, i_k)(其中 (1 le i_j le n)),然后将 (a_{i_1}) 移动到下标 (i_2) 处,将 (a_{i_2}) 移动到下标 (i_3) 处,……,将 (a_{i_{k-1}}) 移动到下标 (i_{k}) 处,将 (a_{i_k}) 移动到下标 (i_1) 处。
换言之,你可以按照如下的顺序轮换元素:(i_1 ightarrow i_2 ightarrow i_3 ightarrow cdots ightarrow i_{k-1} ightarrow i_k ightarrow i_1)。
例如:(n=4, {a_i}={ 10, 20, 30, 40}, i_1=2, i_2=3, i_3=4) ,则操作完成后的 (a) 数列变为 ({ 10, 40, 20, 30})。
你的任务是用操作次数最少的方法将整个数列排序成不降的。注意,所有操作中选定下标的个数总和不得超过 (s) 。如果不存在这样的方法(无解),输出 ( exttt{-1})。
(nle 2 imes 10^5)
Solution
先考虑全排列的情况,可以发现肯定就是每个点的值往它应该在的位置对应的值连边,可以发现的是,肯定是一个又一个的环。然后如果你直接暴力删环,是不行的。因为你可以把环连起来,然后把每个环的末端串起来再做一次就可以了。
考虑拓展到有重复元素的数组,你直接每一个点的权值往这个位置应该对应的值连边即可,然后跑欧拉回路,一个连通块就是一个操作。考虑合并操作,不难发现你合并 (s) 个连通块,那么涉及到的点的数量就会增加 (s) 个,所以你可以合并的连通块数量是一定的,你直接合并就行了。
时间复杂度 (Theta(n+m))。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXN 200005
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,s,un,tot,sum,a[MAXN],b[MAXN],tmp[MAXN];
struct node{
int v,w;
};
vector <node> G[MAXN];
void Add_Edge (int u,int v,int w){
// cout << u << " -> " << v << ": " << w << endl;
G[u].push_back (node {v,w});
}
bool vis[MAXN];
vector <int> ans[MAXN];
void dfs (int u){
vis[u] = 1;
while (!G[u].empty()){
int v = G[u].back().v,w = G[u].back().w;
G[u].pop_back (),dfs (v),ans[tot].push_back (w);
}
}
signed main(){
read (n),read (s);
for (Int i = 1;i <= n;++ i) read (a[i]),tmp[i] = a[i];
sort (tmp + 1,tmp + n + 1);for (Int i = 1;i <= n;++ i) b[i] = (tmp[i] == tmp[i - 1] ? b[i - 1] : b[i - 1] + 1);
un = unique (tmp + 1,tmp + n + 1) - tmp - 1;
for (Int i = 1;i <= n;++ i) a[i] = lower_bound (tmp + 1,tmp + un + 1,a[i]) - tmp;
for (Int i = 1;i <= n;++ i) if (a[i] ^ b[i]) Add_Edge (a[i],b[i],i);tot = 1;
for (Int i = 1;i <= un;++ i) if (!vis[i]){
dfs (i);
if (ans[tot].size()) sum += ans[tot].size(),tot ++;
}
-- tot;
/*cout << tot << " " << sum << endl;
for (Int i = 1;i <= tot;++ i){
for (Int j = 0;j < ans[i].size();++ j) cout << ans[i][j] << " ";cout << endl;
cout << " ----------- " << endl;
}*/
if (sum > s) return puts ("-1"),0;
else if (s - sum <= 1 || tot <= 1){//不能合并环的情况
write (tot),putchar ('
');
for (Int i = 1;i <= tot;++ i){
write (ans[i].size()),putchar ('
');
for (Int j = 0;j < ans[i].size();++ j) write (ans[i][j]),putchar (' ');
putchar ('
');
}
return 0;
}
else{
int anst = tot - min (tot,s - sum) + 2;
write (anst),putchar ('
');
for (Int i = 1;i <= anst - 2;++ i){
write (ans[i].size()),putchar ('
');
for (Int j = 0;j < ans[i].size();++ j) write (ans[i][j]),putchar (' ');
putchar ('
');
}
vector <int> tmp[2];int lst = -1;
for (Int i = tot;i >= anst - 1;-- i) tmp[0].push_back (ans[i][ans[i].size() - 2]),swap (ans[i].back(),lst);
ans[tot].back() = lst;
for (Int i = anst - 1;i <= tot;++ i)
for (Int j = 0;j < ans[i].size();++ j)
tmp[1].push_back (ans[i][j]);
for (Int i = 0;i < 2;++ i){
write (tmp[i].size()),putchar ('
');
for (Int j = 0;j < tmp[i].size();++ j) write (tmp[i][j]),putchar (' ');
putchar ('
');
}
return 0;
}
return 0;
}
T4 「BalkanOI 2018 Day2」Parentrises
Description
「括号串」是一个仅由 (
和 )
构成的字符串。如果在括号串中插入一些 1
和 +
可以将其转化为正确的表达式,该字符串就是一个「良括号串」。例如,(())
和 (())
是良括号串,而 )(
和 (
不是。空字符串可视为良括号串。(就是你们学 Catalan 数时学的那个啊)
将一个括号串(不是良括号串)的每个括号都涂成红绿蓝三种颜色之一,如果有一种方案同时满足:
- 忽略该串的所有蓝色括号后它是良括号串;
- 忽略该串的所有红色括号后它是良括号串;
该串就是 RGB 可读的。
你会接到两类任务之一。任务类型用一个整数 (P) 表示,(P=1) 或 (2)。
- (P=1):你会接到 (T) 组询问,每组询问包含一个括号串,试问该串是否 RGB 可读,如果是,请输出一种染色方案,如果否请输出
impossible
; - (P=2):你会接到 (T) 组询问,每组询问包含一个数 (N),试求:有多少个长度为 (N) 的 RGB 可读的良括号串。输出答案模 ((10^9+7)) 的结果。
Solution
以下 (l,r) 分别表示左右括号的个数
这里只讨论 (P=2)。
首先可以看出,一个括号串能够满足条件当且仅当所有前缀中右括号数量小于等于左括号×2,所有后缀中,左括号个数小于等于右括号数×2。
然后你设 (f_{i,j,k}) 表示前面 (i) 个括号,有 (j) 个左括号,(2r-l) 的最大值为 (k)。那么可以产生贡献的情况当且仅当 (2(i-j)-j-kge 0)。转移显然。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define mod 1000000007
#define MAXN 605
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int ans[305],dp[2][305][MAXN];
void Add(int &a,int b){a = a + b >= mod ? a + b - mod : a + b;}
void init (){
dp[0][0][0] = 1;
for (Int i = 0;i <= 300;++ i){
memset (dp[i + 1 & 1],0,sizeof (dp[i + 1 & 1]));
for (Int j = 0;j <= i;++ j)
for (Int k = 0;k <= 600;++ k)
if (!dp[i & 1][j][k]) continue;
else{
int l = j,r = i - j,f = dp[i & 1][j][k];
if (2 * r - l - k >= 0) Add (ans[i],dp[i & 1][j][k]);
if (2 * l - (r + 1) >= 0) Add (dp[i + 1 & 1][j][max (k,(r + 1) * 2 - l)],f);
Add (dp[i + 1 & 1][j + 1][max (k,r * 2 - (l + 1))],f);
}
}
}
signed main(){
init ();
int P;read (P);
if (P == 2){
int t;read (t);
while (t --> 0){
int n;read (n);
write (ans[n]),putchar ('
');
}
return 0;
}
return 0;
}