昨天不知道为什么有点头痛,就先回去休息了,没更新.(其实是比赛打炸了,心态有点崩( )
Bridging the Gap 2
算法:贪心,数学归纳
第
i
i
i个人最多划的趟数是
(
a
[
i
]
−
1
)
/
2
(a[i]-1)/2
(a[i]−1)/2次
不难发现,将所有人都送往对岸的最小趟数是S=
(
n
−
r
)
/
(
r
−
l
)
(n-r)/(r-l)
(n−r)/(r−l)次
而一趟至少要有l个人会消耗一次趟数
所以总的趟数代价就是S*l
所以能够运送的必要条件是
∑
m
i
n
(
a
[
i
]
,
S
)
>
=
S
∗
l
\sum min(a[i],S)>=S*l
∑min(a[i],S)>=S∗l
接下来运用贪心证明这个是充分条件:
每次我们选择最大的l个人的趟数-1
那么上述式子就满足:
∑
m
i
n
(
a
[
i
]
−
1
,
S
)
>
=
(
S
−
1
)
∗
l
\sum min(a[i]-1,S)>=(S-1)*l
∑min(a[i]−1,S)>=(S−1)∗l
也就是每一趟都有人能过河,直到最后
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+100;
int n,l,r;
int a[N];
int minn;
signed main(){
cin>>n>>l>>r;
if ((n-r)%(r-l) == 0) minn = (n-r)/(r-l); else minn = (n-r)/(r-l)+1;
for (int i = 1; i <= n; i++){
int x; cin>>x; a[i] = (x-1)/2;
}
int S = 0;
for (int i = 1; i <= n; i++) S+=min(a[i],minn);
if (S >= minn*l) cout<<"Yes"; else cout<<"No";
return 0;
}
Rigged Games
对于每一轮,我们对于每一个位置可以二分/倍增出相应的胜利位置,以及记录当前0/1的胜利情况
而后对于每一个位置,我们对于局数b进行倍增,找出对应的位置以及0/1的胜负情况。
思路比较好想,代码细节挺多,实现有点难度
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+10;
int n,a,b;
char s[N];
int z[N],o[N];
typedef pair < int , int > pii;
pii su[N][30];
int Ne[N][30];
bool check(int ed,int x){
int O = o[ed]-o[x-1],Z = z[ed]-z[x-1];
if (O < a && Z < a) return 0; return 1;
}
#define fi first
#define se second
#define mp make_pair
pii Find(int x){
int l = 0 , r = 2*a-1;
while (l+1<r){
int Mid = l+r>>1;
int ed = x+Mid;
int O = o[ed]-o[x-1],Z = z[ed]-z[x-1];
if (O < a && Z < a) l = Mid;
else r = Mid;
}
int po;
if (check(x+l,x)) po = x+l; else po = x+r;
int O = o[po]-o[x-1] , Z = z[po] - z[x-1];
int op;
if (O >= a) op = 0; else op = 1;
po = (po+1)%n;
if (po == 0) po = n;
return mp(po,op);
}
void Pre(){
for (int i = 1; i < 30; i++)
for (int j = 1; j <= n; j++){
Ne[j][i] = Ne[Ne[j][i-1]][i-1];
int x1 = su[j][i-1].fi+su[Ne[j][i-1]][i-1].fi , x2 = su[j][i-1].se+su[Ne[j][i-1]][i-1].se;
su[j][i] = mp(x1,x2);
}
}
int Jump(int x){
int maxx = 0;
for (int i = 0; i < 30; i++){
if ((1<<i) > 2*b-1) break;
maxx = i;
}
int now = x;
int X0 = 0 , X1 = 0;
for (int i = maxx; i >= 0; i--){
int ne = Ne[now][i]; int x0 = su[now][i].fi , x1 = su[now][i].se;
if (X0+x0 < b && X1 + x1 < b) now = Ne[now][i] , X0 = X0+x0 , X1 = X1+x1;
}
X0 = X0+su[now][0].fi , X1 = X1+su[now][0].se;
if (X0 >= b) return 0; else return 1;
}
int main(){
cin>>n>>a>>b;
cin>>(s+1);
for (int i = n+1; i <= n+2*a-1; i++) s[i] = s[i-n];
int len = strlen(s+1);
for (int i = 1; i <= len; i++) o[i] = o[i-1]+(s[i] == '0'),z[i] = z[i-1]+(s[i] == '1');
for (int i = 1; i <= n; i++){
pii po = Find(i);
int x0 = 0,x1 = 0; if (po.se == 0) x0 = 1; else x1 = 1;
su[i][0] = mp(x0,x1);
Ne[i][0] = po.fi;
}
Pre();
for (int i = 1; i <= n; i++){
cout<<Jump(i);
}
return 0;
}
Treasure Hunting
Gourmet choice
有两种思路
第一种就是将所有相等的点;利用并查集缩成一个点,然后拓扑排序
如果是维护小于关系,那就是取max,如果是维护大于关系,那就是取min
第二种就是借鉴查分约束的思想
如果是小于关系,就是最长路
如果是大于关系,就是最短路
相等的情况就是互相连边。
下面代码采用第一种思路
#include<bits/stdc++.h>
using namespace std;
const int N = 5010;
int n,m,tot;
int fa[N];
string s[N];
int getfa(int x){
return x == fa[x]?fa[x]:fa[x] = getfa(fa[x]);
}
void Merge(int x,int y){
int X = getfa(x) , Y = getfa(y);
fa[X] = Y;
}
int du[N];
vector < int > a[N];
#define pb push_back
queue < int > q;
bool vi[N];
int num[N];
int main(){
scanf("%d %d",&n,&m);
tot = n+m;
for (int i = 1; i <= tot; i++) fa[i] = i;
for (int i = 1; i <= n; i++){
cin>>s[i];
for (int j = 0; j < s[i].size(); j++){
char ch = s[i][j];
if (ch == '=') Merge(i,j+n+1);
}
}
for (int i = 1; i <= n; i++){
for (int j = 0; j < s[i].size(); j++){
char ch = s[i][j];
if (ch == '<'){
int X = getfa(i) , Y = getfa(j+n+1);
a[X].pb(Y); du[Y]++;
}
if (ch == '>'){
int X = getfa(i) , Y = getfa(j+n+1);
a[Y].pb(X); du[X]++;
}
}
}
for (int i = 1; i <= tot; i++)
if (du[getfa(i)] == 0 && !vi[getfa(i)])
q.push(getfa(i)) , vi[getfa(i)] = 1,num[getfa(i)] = 1;
int Tot = 0;
for (int i = 1; i <= tot; i++)
if (fa[i] == i) Tot++;
int cnt = 0;
while (q.size()){
cnt++;
int x = q.front(); q.pop(); vi[x] = 0;
for (int i = 0; i < a[x].size(); i++){
int y = a[x][i];
du[y]--; num[y] = max(num[y],num[x]+1);
if (du[y] == 0 && !vi[y])
q.push(y),vi[y] = 1;
}
}
if (cnt != Tot){
cout<<"No"; return 0;
}
cout<<"Yes"<<endl;
for (int i = 1; i <= n; i++) cout<<num[getfa(i)]<<' '; cout<<endl;
for (int i = n+1; i <= tot; i++) cout<<num[getfa(i)]<<' ';
return 0;
}
Famil Door and Brackets
本题要求求方案数,而且他的数据范围n-m<=2000就告诉你这道题大概率是dp
这道题显然跟(与)的个数有关
我们就设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个位置,有j个多余的(的方案数
这个转移就很显然:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
f
[
i
−
1
]
[
j
+
1
]
f[i][j]=f[i-1][j-1]+f[i-1][j+1]
f[i][j]=f[i−1][j−1]+f[i−1][j+1]
分别表示这个位置放(与放)
需要注意的是
f
[
i
]
[
0
]
=
f
[
i
−
1
]
[
1
]
f[i][0]=f[i-1][1]
f[i][0]=f[i−1][1]
而后就是枚举前一个数放的括号的个数以及前一个位置(的个数,利用相等的关系得到右边的)的个数
这边需要明白一点就是)多的个数其实是和(多的个数相等的,翻转一下即可。
然后就是利用乘法原理将两边相乘。
本题还有一个注意点就是由于合法的括号序列要求(的前缀和一直大于0,所以左边括号序列的(个数其实是有最低要求的,就是原串中)最多的个数。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e3+100;
const int P = 1e9+7;
int n,m;
int cnt = 0;
string s;
int f[N][N];
signed main(){
cin>>n>>m;
int now = 0;
f[0][0] = 1;
for (int i = 1; i <= n-m; i++){
for (int j = 0; j <= i; j++)
if (j == 0) f[i][j]+=f[i-1][1],f[i][j]%=P;
else f[i][j] = f[i-1][j-1]+f[i-1][j+1],f[i][j]%=P;
}
cin>>s;
for (int i = 0; i < s.size(); i++){
if (s[i] == '(') now--; else now++;
cnt = max(cnt,now);
}
int ans = 0;
for (int i = 0; i <= n-m; i++){
int Min = max(0ll,cnt);
for (int j = Min; j <= i; j++)
if (j-now <= 2000 && j-now >= 0)ans = ans+((f[i][j]*f[n-m-i][j-now])%P) , ans%=P;
}
cout<<ans;
}