CF264C. Choosing Balls
题意:你有(n)个球,每个都有颜色和权值(c_i)和(w_i)。定义它的子序列的权值为:对于其中的每一个球,如果它在子序列中的上一个球(必须存在)与它同颜色,则贡献(a imes w_i)的权值。否则,贡献(b imes 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 imes w_i)
- (dp_{c_i}' = max dp_k + b imes w_i \, (k ot = 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)
",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 imes m)的01矩阵,有(q)次询问,问它的一个子矩阵中有多少个不包含1的子矩阵。
(n,m leq 40, \, q leq 3 imes 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
",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
",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;
}
小结:总会卡在几个小技巧上……应该是做题经验不够丰富。