感觉最近被茫茫的前路以及紧迫的时间弄得有点乱了阵脚,calm,calm,脚踏实地地一步步往前走吧
实验室环境好差,感觉都要变成游戏厅了。搞不懂,明明在家里玩更舒服,干嘛非要留校呢。
这道题,显而易见的一点是:对于一个字符串,如果它内部已经有合法的括号序列,那么不管怎么进行重排,这些序列是不会受到影响的,也就是说,可以预处理掉这些序列,然后得到的字符串一定都是a个')' 加 b个‘(’的形式(a, b >= 0)。
然后就是本题的精髓了,如何安排这些序列?
考虑x,y两个序列,它们的安排方式有且仅有两种——x在y的前面或者y在x的前面,它们消掉括号的数目分别为 aa = min(x. b, y. a), bb = min(x.a, y. b),那么显然,x,y的先后取决于aa,bb孰大孰小。相等的时候怎么办?答:左括号多的排在前面。理由如下:
分析一下可以知道,aa=bb只有两种情况
情况1:
右括号数目大于左括号,且左括号数目相等,此时,由于左括号数目相等,所以排序策略不会产生影响,是正确的
情况2:
右括号数目小于左括号,且右括号数目相等,此时,左括号数目越多,则越容易产生出合法序列。
ps:另一个小收获,以后做括号匹配是不需要写stack的,数组模拟下就好
#include
#include
#include
#include
using namespace std;
const int N = 100100;
char s[N];
struct Node{
int l, r, sum;
}node[N];
bool cmp(Node x, Node y){
int aa = min(x. l, y. r);
int bb = min(x. r, y. l);
if(aa == bb){
return x. l > y. l;
}
return aa > bb;
}
Node get_val(){
int lens = strlen(s);
Node res;
res. l = 0, res. r = 0, res. sum = 0;
for(int i = 0; i < lens; i ++){
if(s[i] == ')'){
if(res. l > 0){
res. l --;
res. sum ++;
}
else{
res. r ++;
}
}
else{
res. l ++;
}
}
return res;
}
int main(){
int T;
scanf("%d", &T);
while(T --){
int n;
scanf("%d", &n);
for(int i = 0; i < n; i ++){
scanf("%s", s);
node[i] = get_val();
}
sort(node, node + n, cmp);
Node nowl, nxt, tmp;
nowl = tmp = node[0];
for(int i = 1; i < n; i ++){
nxt = node[i];
tmp. l = nowl. l + nxt. l - min(nowl. l, nxt. r);
tmp. r = nowl. r + nxt. r - min(nowl. l, nxt. r);
tmp. sum = nowl. sum + nxt. sum + min(nowl. l, nxt. r);
nowl = tmp;
}
printf("%d\n", nowl. sum * 2);
}
return 0;
}
一开始写了个set,超时了,感觉这题有种用小根堆的意思,但是不知道该怎么用
R[l]:每一个l所对应的r
然后从1扫到n,last代表上一个区间的起点,nowl代表在该位置之前(包括该位置)的数字已经填好。剩下的就是一个模拟的过程。至于这个复杂度。。玄学。现在敲代码会犯一些很愚蠢的错误,改。
#include
#include
#include
#include
#include
using namespace std;
const int N = 100100;
int a[N];
int R[N];
int n, m;
priority_queue , greater > pq;
int main(){
int T;
scanf("%d", &T);
while(T --){
scanf("%d %d", &n, &m);
while(! pq. empty()){
pq. pop();
}
for(int i = 1; i <= n; i ++){
pq. push(i);
R[i] = i;
}
int x, y;
for(int i = 1; i <= m; i ++){
scanf("%d %d", &x, &y);
R[x] = max(R[x], y);
}
int last = 1, nowl = 0;
for(int i = 1; i <= n; i ++){
if(nowl >= R[i]){
continue;
}
while(last < i){
pq. push(a[last ++]);
}
while(nowl < R[i]){
a[nowl + 1] = pq. top();
pq. pop();
nowl ++;
}
}
for(int i = 1; i <= n; i ++)
printf("%d%c", a[i], i == n ? '\n' : ' ');
}
return 0;
}
本质上和A一样,找规律,毕竟咱不会证明。。
可是即使是找规律,还是有种啃不动的感觉,对着整整一张表的数据茫然发呆
看了大佬的题解才知道应该怎么找这个规律:找每个数出现次数和n的关系,然后出现次数相同的数构成一个等差数列,用公式求和即可。其中还有各种各样的玄学操作,我只能把它认为是巧合??就像重复次数等于末尾1的位置。
然后是这道题的代码,感觉各种玄学,就像是在拼凑一样,emmm,话说找规律本身也是一种玄学吧
#include
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const ll inv2 = 500000004;
ll pw[80]; //pw[i]=2^i
ll cnt[80];//cnt[i]=2^(i+1)-1
ll n, pos;
void init(){
pw[0] = cnt[0] = 1;
for(int i = 1;i <= 62; i ++){ //足够涵盖所有数据
pw[i] = pw[i - 1] * 2;
cnt[i] = 2 * pw[i] - 1;
}
}
ll getsum(ll p){//计算序列中出现的所有1,2...p的和
ll ans = 0;
for(ll i = 1; i <= p; i *= 2){
ll num = (p - i) / (2 * i); //首项=i, num=项数-1
ll last = i + num * (2 * i);//公差是2*i, 算出末项
num = (num + 1) % mod;
ll tmp = (i + last) % mod;//等差数列求和S=(首项+末项)*项数/2
tmp = tmp * num % mod;
tmp = tmp * inv2 % mod;
tmp = tmp * (__builtin_ffsll(i)) % mod;//乘以对应的出现次数,右边起第一个1的位置
ans = (ans + tmp) % mod;
}
return (1 + ans) % mod;//加上第一项被忽略的1
}
int main(){
init();
int T;
scanf("%d", &T);
while(T --){
scanf("%lld", &n);
n --;
if(n == 0){
puts("1");
continue;
}
pos = 0;
ll tmp = n;
//在log的时间内求出pos
for(int i = 62; i >= 0; i --){
if(tmp >= cnt[i]){
tmp -= cnt[i];
pos += pw[i];
}
}
ll ans = getsum(pos);
if(tmp)
ans = (ans + tmp % mod * (pos + 1) % mod) % mod;//如果有剩下的几项,就再加上, 下一个数一定比pos大1,且一共有tmp个这样的数
printf("%lld\n", ans);
}
return 0;
}
刚开始补题:这题一定可以挖出很多知识,好好看好好学
a few moment later ... :dls板子真好用
具体思路还是看dls直播吧,我只是注释了下代码。听起来感觉dls的做法很有道理,但是原因又想不明白,单纯是结点的概率乘积再乘[0,1]的期望为什么就是答案都半知半解。。。
#include
using namespace std;
typedef long long ll;
const int N = 1000100;
const ll mod = 1000000007;
ll ans;
ll inv[N];
struct Tree {
int root, top, n;
int sta[N], l[N], r[N];
bool vis[N]; //用来找根节点
void build(int *num, int nn) {
n = nn;
top = 0;
memset(l, 0, sizeof(int) * (n + 1));
memset(r, 0, sizeof(int) * (n + 1));
memset(vis, 0, sizeof(bool) * (n + 1));
for(int i = 1; i <= n; i ++) {
int tmp = top;
//沿最右端的这条链,按从叶子到根的顺序寻找第一个小于i的位置
while(top > 0 && num[sta[top - 1]] < num[i]) {
top --;
}
if(top != 0) {
r[sta[top - 1]] = i;
}
if(top < tmp) {
l[i] = sta[top];
}
sta[top ++] = i;
}
//如果一个结点不是任何一个结点的孩子,那么它就是根节点
for(int i = 1; i <= n; i ++) {
vis[l[i]] = vis[r[i]] = true;
}
for(int i = 1; i <= n; i ++) {
if(! vis[i]) {
root = i;
}
}
}
int dfs(int x) {
//如果这棵树有(加上根)有cnt个结点,那么根节点是最大值的概率就是1/cnt
int cnt = 1;
if(l[x] != 0) {
cnt += dfs(l[x]);
}
if(r[x] != 0) {
cnt += dfs(r[x]);
}
ans = (ans * inv[cnt]) % mod;
return cnt;
}
};
int T, n;
int num[N];
Tree t;
//线性时间求逆元
void Init() {
inv[1] = 1;
for(int i = 2; i < N; ++i) {
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
}
int main() {
Init();
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
ans = (n * inv[2]) % mod; //均匀分布的概率密度
for(int i = 1; i <= n; ++i) {
scanf("%d", &num[i]);
}
t. build(num, n);
t. dfs(t. root);
printf("%I64d\n", ans);
}
return 0;
}