【做题】提高组过关测试题2

CF264C. Choosing Balls

题意:你有\(n\)个球,每个都有颜色和权值\(c_i\)\(w_i\)。定义它的子序列的权值为:对于其中的每一个球,如果它在子序列中的上一个球(必须存在)与它同颜色,则贡献\(a\times w_i\)的权值。否则,贡献\(b \times w_i\)的权值。其中,\(a\)\(b\)都是常量。

\(q\)次询问,每次给出\(a\)\(b\),问所有子序列中最大的权值是多少。

\(n \leq 10^5, \, q \leq 500\)

dp是显然的。我们记dp[c]为目前以颜色c结尾的子序列中,最大的权值。那么,我们枚举每一个球。设当前枚举到第\(i\)个球,就能得到

  • \(dp_{c_i} '= dp_{c_i} + a \times w_i\)
  • \(dp_{c_i}' = \max dp_k + b \times w_i \, (k \not = c_i)\)

在求\(\max dp_k\)上有一个小技巧。我们记录dp值最大的两种颜色,那么,其中至少有一个不是\(c_i\)。这样,对于每次询问,我们多能\(O(n)\)地完成dp。

时间复杂度\(O(nq)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
template <typename tp>
inline void read(tp& x) {
  x = 0;
  char tmp;
  bool key = 0;
  for (tmp = getchar() ; !(tmp>='0'&&tmp<='9') ; tmp=getchar())
    key = (tmp == '-');
  for ( ; tmp >= '0' && tmp <= '9' ; tmp = getchar())
    x = (x<<1) + (x<<3) + tmp - '0';
  if (key) x = -x;
}
const int N = 100010, INF = 0x3f3f3f3f3f3f3f3f;
int n,q,dp[N],c[N],val[N],a,b,mx,mx1,pm,pm1,vis[N];
signed main() {
  read(n), read(q);
  for (int i = 1 ; i <= n ; ++ i)
    read(val[i]);
  for (int i = 1 ; i <= n ; ++ i)
    read(c[i]);
  for (int i = 1 ; i <= q ; ++ i) {
    read(a), read(b);
    for (int j = 1 ; j <= n ; ++ j)
      dp[j] = 0, vis[j] = 0;
    mx = mx1 = 0;
    pm = pm1 = 0;
    for (int j = 1 ; j <= n ; ++ j) {
      int x = -INF,y = -INF,z = -INF,v;
      if (pm != c[j])
	x = b * val[j] + mx;
      if (pm1 != c[j])
	y = b * val[j] + mx1;
      if (vis[c[j]])
	z = val[j] * a + dp[c[j]];
      v = max(x,y); v = max(v,z);
      if (!vis[c[j]]) dp[c[j]] = v;
      else dp[c[j]] = max(dp[c[j]],v);
      vis[c[j]] = 1;
      if (v == dp[c[j]]) {
	if (pm == c[j]) mx = v;
	else if (v > mx) {
	  mx1 = mx;
	  pm1 = pm;
	  mx = v;
	  pm = c[j];
	} else if (v > mx1) {
	  mx1 = v;
	  pm1 = c[j];
	}
      }
    }
    int ans = 0;
    for (int j = 1 ; j <= n ; ++ j)
      if (vis[j]) ans = max(ans,dp[j]);
    cout << ans << endl;
  }
  return 0;
}

CF212C. Cowboys

题意:\(n\)个人站成一个圆圈。每个人要么面向顺时针,要么面向逆时针。前者用A来表示,后者用B表示。如果两个人面对面地站着,那么,在下一秒他们都对改变自己面朝的方向。给出一秒后的状态,问在这一秒有多少种可能的站法。

\(n \leq 100\)

考虑dp。令dp[i,a]为放到第\(i\)位且第\(i\)位是\(a\)的方案数。分类讨论一下就能转移了。

此外,还要讨论环的开头和结尾是否面对面。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 110;
int dp[N][2],n,val[N],ans;
char s[N];
signed main() {
  scanf("%s",s+1);
  n = strlen(s+1);
  for (int i = 1 ; i <= n ; ++ i)
    val[i] = (s[i] == 'A');
  dp[1][val[1]] = 1;
  dp[0][0] = 1;
  for (int i = 2 ; i <= n ; ++ i) {
    if (val[i]) {
      dp[i][1] += dp[i-1][0] + dp[i-1][1];
      if (!val[i-1] && i != 2) dp[i][0] += dp[i-2][0] + dp[i-2][1];
    } else dp[i][0] += dp[i-1][0];
    // printf("%lld:(%lld,%lld)\n",i,dp[i][0],dp[i][1]);
  }
  if (val[1]) ans += dp[n][0] + dp[n][1];
  else {
    ans += dp[n][0];
    if (val[2]) {
      memset(dp,0,sizeof dp);
      dp[2][0] = 1;
      for (int i = 3 ; i <= n ; ++ i) {
	if (val[i]) {
	  dp[i][1] += dp[i-1][0] + dp[i-1][1];
	  if (!val[i-1] && i != 2) dp[i][0] += dp[i-2][0] + dp[i-2][1];
	} else dp[i][0] += dp[i-1][0];
      }
      ans += dp[n][0] + dp[n][1];
    }
  }
  memset(dp,0,sizeof dp);
  if (val[1] == 1 && val[n] == 0) {
    dp[1][0] = 1;
    for (int i = 2 ; i < n ; ++ i) {
      if (val[i]) {
	dp[i][1] += dp[i-1][0] + dp[i-1][1];
	if (!val[i-1]) dp[i][0] += dp[i-2][0] + dp[i-2][1];
      } else dp[i][0] += dp[i-1][0];
    }
    ans += dp[n-1][0] + dp[n-1][1];
  }
  cout << ans << endl;
  return 0;
}

CF160D. Edges in MST

题意:有一个\(n\)个点,\(m\)条边的联通图,边有边权。求每一条边与这个图的最小生成树的关系(在所有最小生成树中;在某些最小生成树中;不在任何最小生成树中)。

\(n, m \leq 10^5\)

考虑kruskal的运算过程。那么,我们显然要对边权相等的边合起来考虑。

假设当前有若干个联通块,则连接同一个联通块的边,显然不会在任何最小生成树中。(加入它就会形成环,且环上其他边的边权都小于它)

剩下的边至少出现在一个最小生成树中。若某条边出现在所有最小生成树中,则加入所有权值小于等于它的边后,它不在任何环上。也就是说,它是桥。因此,我们把目前的联通块缩点,加入这些边后用targan判断是否是桥就可以了。这里还要特判重边。

时间复杂度\(O(n \log n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
struct edge {
  int a,b,v,id;
  bool operator < (const edge& x) const {
    return v < x.v;
  }
} ed[N];
int n,uni[N],m,ans[N];
vector<int> recv,rece;
multiset<pair<int,int> > st;
struct cont {
  int la,b;
} con[N << 1];
int tot,fir[N];
void add(int from,int to) {
  con[++tot] = (cont) {fir[from],to};
  fir[from] = tot;
}
int dfn[N],low[N],top,sta[N],col[N],ccnt;
void dfs(int pos,int fa) {
  sta[low[pos] = dfn[pos] = ++top] = pos;
  for (int i = fir[pos] ; i ; i = con[i].la) {
    if (col[con[i].b] || con[i].b == fa) continue;
    if (!dfn[con[i].b]) {
      dfs(con[i].b,pos);
      low[pos] = min(low[con[i].b],low[pos]);
    } else low[pos] = min(low[pos],dfn[con[i].b]);
  }
  if (low[pos] == dfn[pos]) {
    ++ ccnt;
    while (top >= dfn[pos])
      col[sta[top--]] = ccnt;
  }
}
void init() {
  for (int i = 0 ; i < (int)recv.size() ; ++ i) {
    int pos = recv[i];
    fir[pos] = dfn[pos] = low[pos] = col[pos] = 0;
  }
  recv.clear();
  tot = ccnt = top = 0;
  rece.clear();
}
int get_fa(int pos) {
  return uni[pos] != pos ? uni[pos] = get_fa(uni[pos]) : pos;
}
int main() {
  int x,y,z;
  scanf("%d%d",&n,&m);
  for (int i = 1 ; i <= m ; ++ i) {
    scanf("%d%d%d",&x,&y,&z);
    ed[i] = (edge) {x,y,z,i};
  }
  sort(ed+1,ed+m+1);
  for (int i = 1 ; i <= n ; ++ i)
    uni[i] = i;
  for (int i = 1 ; i <= m ; ++ i) {
    int tmp = ed[i].v, rec = i;
    st.clear();
    for ( ; i <= m && ed[i].v == tmp ; ++ i) {
      if (get_fa(ed[i].a) == get_fa(ed[i].b))
	ans[ed[i].id] = -1;
      else {
	int x = get_fa(ed[i].a), y = get_fa(ed[i].b);
	if (x > y) swap(x,y);
	st.insert(make_pair(x,y));
	recv.push_back(x);
	recv.push_back(y);
      }
    }
    i --;
    for (int j = rec ; j <= i ; ++ j) {
      if (get_fa(ed[j].a) != get_fa(ed[j].b)) {
	int x = get_fa(ed[j].a), y = get_fa(ed[j].b);
	if (x > y) swap(x,y);
	add(x,y);
	add(y,x);
	if (st.count(make_pair(x,y)) > 1) {
	  ans[ed[j].id] = 2;
	} else rece.push_back(j);
      }
    }
    for (int j = 0 ; j < (int)recv.size() ; ++ j)
      if (!dfn[recv[j]]) dfs(recv[j],0);
    for (int j = 0 ; j < (int)rece.size() ; ++ j) {
      int k = rece[j];
      if (col[get_fa(ed[k].a)] != col[get_fa(ed[k].b)])
	ans[ed[k].id] = 1;
      else ans[ed[k].id] = 2;
    }
    init();
    for ( ; rec <= i ; ++ rec) {
      int x = ed[rec].a, y = ed[rec].b;
      x = get_fa(x), y = get_fa(y);
      if (x != y)
	uni[x] = y;
    }
  }
  for (int i = 1 ; i <= m ; ++ i) {
    if (ans[i] == -1) puts("none");
    else if (ans[i] == 1) puts("any");
    else if (ans[i] == 2) puts("at least one");
    else assert(0);
  }
  return 0;
}

CF372B. Counting Rectangles is Fun

题意:给出一个\(n \times m\)的01矩阵,有\(q\)次询问,问它的一个子矩阵中有多少个不包含1的子矩阵。

\(n,m \leq 40, \, q \leq 3 \times 10^5\)

关键就在于一个小技巧,预处理时,我们枚举所有子矩阵,只计算出底与它的底在同一条直线上的子矩阵个数。对这个做前缀和就能得到答案。而通过使用单调栈,很容易把预处理复杂度优化到\(O(n^5)\)\(O(n^4)\)

时间复杂度\(O(n^5 + m)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 45;
int a[N][N],n,m,q,top,cur,ans[N][N][N][N];
char tmp[N];
struct data {
  int l,r,v;
} sta[N];
int main() {
  int x,y,z,d;
  scanf("%d%d%d",&n,&m,&q);
  for (int i = 1 ; i <= n ; ++ i) {
    scanf("%s",tmp+1);
    for (int j = 1 ; j <= m ; ++ j)
      a[i][j] = tmp[j] - '0';
  }
  for (int i = 1 ; i <= n ; ++ i)
    for (int j = i ; j <= n ; ++ j) {
      for (int k = 1 ; k <= m ; ++ k) {
	top = 0;
	cur = 0;
	for (int p = k ; p <= m ; ++ p) {
	  int tmp = j - i + 1;
	  for (int t = i ; t <= j ; ++ t)
	    if (a[t][p] == 1) tmp = j - t;
	  data tp = (data) {p,p,tmp};
	  while (sta[top].v >= tp.v && top) {
	    tp.l = sta[top].l;
	    cur -= (sta[top].r - sta[top].l + 1) * sta[top].v;
	    top --;
	  }
	  sta[++top] = tp;
	  cur += (tp.r - tp.l + 1) * tp.v;
	  ans[i][j][k][p] = ans[i][j][k][p-1] + cur;
	}
      }
    }
  for (int i = 1 ; i <= n ; ++ i)
    for (int k = 1 ; k <= m ; ++ k)
      for (int p = k ; p <= m ; ++ p)
	for (int j = i ; j <= n ; ++ j)
	  ans[i][j][k][p] += ans[i][j-1][k][p];
  for (int i = 1 ; i <= q ; ++ i) {
    scanf("%d%d%d%d",&x,&y,&z,&d);
    printf("%d\n",ans[x][z][y][d]);
  }
  return 0;
}

CF510E. Fox And Dinner

题意:有\(n\)个元素,每个的权值为\(a_i\)。你需要把它们分成若干组,每一组的元素排成一个环,且满足:

  • 每一组都至少有3个元素。
  • 每一组的环上,每一对相邻元素的权值和为奇质数。

要求判断是否有解,若有解则输出任意一组解。

\(n \leq 200, \, a_i \leq 10^4\)

显然,两个元素如果相加为奇数,那么一定是一奇一偶。因此,我们可以把元素按奇偶性分组,然后依照相加能否为奇质数连边。这样,问题就变成了二分图的环覆盖。

设一个结点在二分图上的度数为与它匹配的边数。考虑一个点如果在环上,那么它的度数为2。可以发现,存在方案使二分图上的每一个结点度数为2,等价于存在一个环覆盖的方案。这个结论必要性显然,而在充分性上,若有结点点不在环上,则一定存在结点的度数小于2;若有结点在多个环上,则一定存在结点的度数大于2。

让每个点的度数都变成2,这个可以用最大流解决。至于输出方案,只要在图上dfs就可以了。

时间复杂度\(O(n^4 + m)\),其中\(m\)\(a_i\)的权值范围。

#include <bits/stdc++.h>
using namespace std;
const int N = 610, MAX = 20000, INF = 0x3f3f3f3f;
struct edge {
  int la,b,cap;
} con[N * N];
int tot=1,fir[N];
void add(int from,int to,int capc) {
  con[++tot] = (edge) {fir[from],to,capc};
  fir[from] = tot;
  con[++tot] = (edge) {fir[to],from,0};
  fir[to] = tot;
}
int cur[N], dis[N], n, st, en, vis[N];
int dfs(int pos,int imp) {
  if (pos == en || (!imp)) return imp;
  int expo = 0, tmp;
  for (int &i = cur[pos] ; i ; i = con[i].la) {
    if (dis[con[i].b] == dis[pos] + 1) {
      tmp = dfs(con[i].b,min(imp,con[i].cap));
      con[i].cap -= tmp;
      con[i^1].cap += tmp;
      expo += tmp;
      imp -= tmp;
      if (!imp) break;
    }
  }
  return expo;
}
bool bfs() {
  static queue<int> q;
  while (!q.empty()) q.pop();
  memset(dis,0,sizeof dis);
  for (int i = 1 ; i <= n ; ++ i)
    cur[i] = fir[i];
  dis[st] = 1;
  q.push(st);
  for (int pos ; !q.empty() ; q.pop()) {
    pos = q.front();
    for (int i = fir[pos] ; i ; i = con[i].la) {
      if (con[i].cap && (!dis[con[i].b])) {
	dis[con[i].b] = dis[pos] + 1;
	q.push(con[i].b);
      }
    }
  }
  if (!dis[en]) return 0;
  return 1;
}
int a[N],isp[MAX + 10], pri[MAX], pcnt, ans, cnt;
vector<int> rec[N];
void prework() {
  for (int i = 2 ; i <= MAX ; ++ i) {
    if (!isp[i]) pri[++pcnt] = i;
    for (int j = 1 ; j <= pcnt && pri[j] * i <= MAX ; ++ j) {
      isp[pri[j] * i] = 1;
      if (i % pri[j] == 0) break;
    }
  }
}
void sdf(int pos,int fa) {
  rec[cnt].push_back(pos);
  vis[pos] = 1;
  for (int i = fir[pos] ; i ; i = con[i].la) {
    if (con[i].b <= n-2 && ((con[i].cap == 0 && (i&1) == 0) || (con[i].cap == 1 && (i&1) == 1))) {
      if (con[i].b == fa || vis[con[i].b]) continue;
      sdf(con[i].b,pos);
    }
  }
}
int main() {
  scanf("%d",&n);
  for (int i = 1 ; i <= n ; ++ i)
    scanf("%d",&a[i]);
  st = ++n;
  en = ++n;
  prework();
  for (int i = 1 ; i <= n-2 ; ++ i) {
    if (a[i]&1) {
      add(st,i,2);
      for (int j = 1 ; j <= n-2 ; ++ j)
	if (!isp[a[i] + a[j]]) add(i,j,1);
    } else add(i,en,2);
  }
  while (bfs())
    ans += dfs(st,INF);
  if (ans < n - 2) puts("Impossible");
  else {
    for (int i = 1 ; i <= n - 2 ; ++ i) if (!vis[i]) {
	++ cnt;
	sdf(i,0);
      }
    printf("%d\n",cnt);
    for (int i = 1 ; i <= cnt ; ++ i) {
      printf("%d ",(int)rec[i].size());
      for (int j = 0 ; j < (int)rec[i].size() ; ++ j)
	printf("%d ",rec[i][j]);
      puts("");
    }
  }
  return 0;
}

小结:总会卡在几个小技巧上……应该是做题经验不够丰富。
posted @ 2018-07-12 14:41  莫名其妙的aaa  阅读(283)  评论(0编辑  收藏  举报