###Educational Codeforces Round 115 (Rated for Div. 2)
#####A - Computer Game
思路:dfs暴力搜索。
const int N = 1010;
int _,n;
char a[3][N];
bool st[3][N];
int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1};
void dfs(int x,int y){
st[x][y] = true;
for(int i=0;i<=7;++i){
int idx = l[i] + x,idy = r[i] + y;
if(idx > 2 or idx <= 0 or idy > n or idy <= 0)continue;
if(st[idx][idy])continue;
if(a[idx][idy] == '1')continue;
dfs(idx,idy);
}
}
void solve(){
read(_);
while(_--){
read(n);
memset(a,-1,sizeof a);
memset(st,0,sizeof st);
scanf("%s%s",a[1]+1,a[2]+1);
if(a[1][1] == '0')
dfs(1,1);
if(st[2][n] == 1)puts("YES");else puts("NO");
}
}
思路:暴力枚举答案,然后设答案为 那么检测答案是否合法。只有当所有人都至少在其中一天内有空,并且在这两天有空的总人数均大于等于 ,那么答案就合法。如果不理解可以自己画图感受一下。
const int N = 100100;
int _,n;
int a[N][6];
bool st[33];
int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1};
void solve(){
read(_);
while(_--){
read(n);
rep(i,1,n){
rep(j,1,5){
read(a[i][j]);
}
}
bool flag = false;
for(int i=1;i<(1<<5);++i){
int cnt = 0;
int now[55],hh = 0;
for(int j=0;j<=4;++j){
if((i>>j)&1){
now[++hh] = j+1;cnt++;
}
}
bool judge = true;
int x[33];x[1] = x[2] = 0;
if(cnt == 2){
//rep(j,0,4)write((i>>j)&1);pc('\n');
for(int j=1;j<=n;++j){
if(!a[j][now[1]] and !a[j][now[2]]){
judge = false;break;
}
x[1] += (a[j][now[1]] == 1);
x[2] += (a[j][now[2]] == 1);
}
}else continue;
if(x[1] < n/2 or x[2] < n/2)judge = false;
if(judge){flag = true;break;}
}
if(flag)puts("YES");else puts("NO");
}
}
思路:因为他让选择两个数,我们首先判断是否有答案,由平均数定义可得:
所以:
因为数组是整数,那么首先判断是否可以为整数,然后开个桶记录所有个数,依次枚举每一个作为答案。即可得到答案
复杂度
const int N = 200100;
int _,n;
ll a[N];
bool st[33];
int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1};
map<ll,ll>S;
void solve(){
read(_);
while(_--){
S.clear();
read(n);
ll Sum = 0ll;
rep(i,1,n){
read(a[i]);S[a[i]]++;
Sum += a[i];
}
if((Sum * (2ll))%n){
puts("0");continue;
}
ll x = (Sum * (2ll))/n;
//printf("%lld\n",x);
ll ans = 0;
rep(i,1,n){
ll now = x - a[i] ;
ll res = S[now];
if(now == a[i]){
res--;
}
ans+=res;
}
write(ans/2);pc('\n');
}
}
思路:这个题是让我们选合法三元组数,那么正难则反,我们考虑不符合的个数。首先我们发现如果三元组有一个属性全部相同,那么该三元组一定合法。比如属性 ,那么属性一定是不相同的,因为如果相同就不满足题目所给限制:任意两个问题属性和属性至少有一个不同。那么我们不符合的个数里只有这种形式,即两个属性是两个相同的,那我们发现我们可以枚举作为中间的,因为在中间的元素右边必定与其属性相同,左边必定与其属性相同,开个桶分别记录两个属性满足的个数,所以不符合的个数应该是两边相乘得到的结果。最后用总个数减去不符合的个数。
复杂度
const int N = 200100;
int _,n;
ll a[N];
bool st[33];
int l[N],r[N];
map<ll,ll>S;
pair<int,int>arr[N];
void solve(){
read(_);
while(_--){
read(n);
fill(l,l+1+n,0);fill(r,r+1+n,0);
ll sum = (1ll *n*(n-1)*(n-2))/6;
rep(i,1,n){
int x,y;
read(x);read(y);
l[x]++;r[y]++;
arr[i] = {x,y};
}
ll res = 0ll;
rep(i,1,n){
int x = arr[i].first,y = arr[i].second;
res += 1ll*(l[x]-1)*(r[y]-1);
}
write(sum-res);pc('\n');
}
}
思路:这个题感觉比较好想,重点应该在代码实现上,将梯子向上、向下搜索。然后在第一个不自由的格子停住,然后相乘维护答案。因为询问,并且每次询问暴力复杂度为 总复杂度为 ,可以通过。
const int N = 1010;
int _, n, m, q;
bool st[N][N];
ll a[N];
int l[] = {-1, 0, 1, 0}, r[] = {0, -1, 0, 1};
ll ans;
void work(int x, int y) {
// 2~3 1~4
int idx = x, idy = y;
ll a1 = 0, a2 = 0, b1 = 0, b2 = 0;
int now = 0;
bool tmp = st[x][y];
st[x][y] = false;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
a1++;
idx += l[now];
idy += r[now];
now++;
if (now >= 2) now = 0;
}
now = 1;
idx = x, idy = y;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
a2++;
idx += l[now];
idy += r[now];
now++;
if (now >= 2) now = 0;
}
now = 2;
idx = x, idy = y;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
b2++;
idx += l[now];
idy += r[now];
now++;
if (now >= 4) now = 2;
}
idx = x;
idy = y;
now = 3;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
b1++;
idx += l[now];
idy += r[now];
now++;
if (now >= 4) now = 2;
}
if (tmp == true) {
ans += a1 * b1 + a2 * b2 - 1;
} else {
ans -= a1 * b1 + a2 * b2 - 1;
}
st[x][y] = !tmp;
}
void solve() {
read(n);
read(m);
read(q);
rep(i, 1, max(n, m)) {
ll x1 = max(1ll * (n - i + 1) * (m - i + 1), 0ll);
if (i != 1) x1 *= 2ll;
ll x2 = max(1ll * (n - i + 1) * (m - (i + 1) + 1), 0ll) +
max(0ll, 1ll * (n - (i + 1) + 1) * (m - i + 1));
// printf("%d:: %lld %lld\n",i,x1,x2);
ans += x1 + x2;
}
rep(test, 1, q) {
int x,y;
read(x);read(y);
work(x,y);
write(ans);pc('\n');
}
}
思路:我们定义$ “(”$的权值为1, 的权值为-1。我们定义合法字符串为的所有前缀的权值和的最小值大于等于0。只有合法字符串才能够更新答案,否则该字符串的答案就已经确定了,就没必要再进行更新。
- 例如:是一个非法字符串,是一个合法字符串。
考虑动态规划,因为最多只有20个串,所以我们可以用二进制来表示集合。设表示选择集合的最佳答案。我们再维护一个数组,表示在当前集合取最佳答案时的权值和为多少。特别的,如果为非法字符串,那么。考虑转移,根据一般动态规划特性,我们应该枚举该集合中哪一个串是出现在最后。然后用这个来更新答案。我们假设枚举的串编号为 那么我们转移的集合应该是,那么分类讨论:
- ,那么说明已经是非法字符串了,答案不会再增加,所以不用转移。
- ,注:这里的是指字符串的最小前缀和。那么这表明,虽然并不是非法字符串,但是最后加入字符串之后,就变成了非法字符串,那么我们可以更新 ,维护两个桶,第一个桶简单代表前缀和的出现次数,第二个桶代表第一次出现比小的前缀和时,的个数。我们这里主要是用第二个桶来完成更新。因为我们已经知道该集合的权值和,那么又知道了经过这一次更新,将变为非法字符串,那么在字符串中,肯定有一个位置,在这个位置之前,原串是合法字符串,在这个位置之后,原串是非法字符串,那我们第二个桶记录总是在第一个桶第一次记录时,就是表明在前缀和最低降到时,有多少个位置的前缀和为。我们不难写出转移方程 。
- ,这说明经过这轮更新之后,依旧是合法字符串,那么我们可以更新该字符串的数组,并且用第一个桶进行维护答案。
因为维护桶用的是,所以复杂度
string a[33];
int n;
int mi[33], Sum[33];
int f[(1 << 21)], g[(1 << 21)];
unordered_map<int, int> S1[22], S2[22];
void solve() {
ios::sync_with_stdio(false);
cin >> n;
rep(i, 0, n - 1) {
cin >> a[i];
int now = 0;
for (auto j : a[i]) {
if (j == '(')
now++;
else
now--;
mi[i] = min(mi[i], now);
//记录前缀个数
S1[i][now]++;
//记录第一次不能统计now+1时的数量
if (S1[i][now] == 1) {
S2[i][now + 1] = S1[i][now + 1];
}
}
Sum[i] = now;
}
int ans = 0;
for (int i = 1; i < (1 << n); ++i) {
g[i] = -1;
//枚举第j个做集合i的最后一个字符串
for (int j = 0; j < n; ++j) {
if ((i >> j) & 1) {
int id = i ^ (1 << j);
if (g[id] < 0) {
continue;
} else if (g[id] + mi[j] < 0) {
int sum = S2[j][-g[id]] + f[id];
ans = max(ans, sum);
} else {
int res = S1[j][-g[id]] + f[id];
if (f[i] < res) {
f[i] = res;
}g[i] = Sum[j] + g[id];
ans = max(ans,f[i]);
}
}
}
}
write(ans);pc('\n');
}
思路:设且,那么的长度可以有以下几种
那么第一种情况,我们可以枚举在串中所有长度为的串来作为答案。重点考虑第二种情况,我们枚举所有长度为的串为来判断,我们可以发现确定之后,的长度其实是由与的最长公共前缀决定的,因为没有的存在,所以即为的长度,在确定的长度以及的位置后,我们可以取出来进行判断。长度可以在的范围内都试一下。
那么我们取出之后,接下来需要判断是否等于,高精度复杂度明显不行,我们发现可以利用字符串哈希的方式的判断是否合法。问题就解决了。
复杂度
(摆烂了,代码不写了