一句话题意:
为国内航班和国际航分配廊桥的数量,使得最终停在廊桥的飞机总数最大。廊桥的使用原则是先到先得。
关键点:
分析
通过上面的分析和结论,我们发现,用这样的过程来模拟可以得到第一个廊桥最多的服务次数,前两个廊桥最多的服务次数,依次类推。我们得到了不同数量的廊桥能服务的最大飞机数。
然后就暴力枚举分配廊桥数量,取最大就可以了。
错误思路
按照以上思路写代码,会出现一个不好解决的问题: 要在多个可用的旧廊桥中找编号最小的廊桥。使用暴力方法需要O(n) 的扫描,因此复杂度退化为 O ( n 2 ) O(n^2) O(n2)。
这个操作很容易被抽象出来:在所有小于某个时间的编号中取最小值, 这明显是一个类似二维偏序的问题,所以使用树状数组来维护每个时间点对应的廊桥编号,动态取前缀最小值即可。
同是为了防止炸空间我还加了离散化。
#include
using namespace std;
typedef long long ll;
template<class T> bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T> bool chmin(T &a, const T &b) { if (a>b) { a=b; return 1; } return 0; }
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LLINF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 500005;
const int MAXM = 2000005;
ll vis[MAXN], tim[MAXN];
int n, m1, m2;
struct BIT {
int C[MAXN];
int N;
inline int lowbit(int x) { return x & (-x); }
void init(int _N) {
N = _N;
memset(C, 0x3f, sizeof(C));
}
int getMin(int x) {
int ret = INF;
while(x > 0) {
ret = min(ret, C[x]);
x -= lowbit(x);
}
return ret;
}
void updateMin(int x) {
while(x<=N) {
C[x] = tim[x];
for(int i=1;i<lowbit(x); i<<=1) C[x] = min(C[x], C[x-i]);
x += lowbit(x);
}
}
void DEBUG() {
for(int i = 1; i <= N; i++) {
cout << i << ": " << C[i] << endl;
}
}
}Bit;
void handle(vector<pair<int, int> > &A, int F[]) {
Bit.init(2*(m1+m2) + 2);
sort(A.begin(), A.end());
memset(tim, 0x3f, sizeof(tim));
memset(vis, 0, sizeof(vis));
int cnt = 0;
for(int i = 0; i < A.size(); i++) {
int id = Bit.getMin(A[i].first);
// 没有可以停靠的廊桥
if(id == INF) {
cnt ++;
F[cnt] = 1;
tim[A[i].second] = cnt;
vis[cnt] = A[i].second;
Bit.updateMin(A[i].second);
}
// 有可以停靠的廊桥
else {
tim[vis[id]] = INF;
Bit.updateMin(vis[id]);
vis[id] = A[i].second;
tim[A[i].second] = id;
Bit.updateMin(A[i].second);
F[id] ++;
}
// Bit.DEBUG();
}
}
vector<pair<int, int> > A1, A2;
int F1[MAXN], F2[MAXN];
vector<int> pool;
int main(){
cin >> n >> m1 >> m2;
for(int i = 0; i < m1; i++) {
int l, r; cin >> l >> r;
A1.push_back(make_pair(l, r));
pool.push_back(l);
pool.push_back(r);
}
for(int i = 0; i < m2; i++) {
int l, r; cin >> l >> r;
A2.push_back(make_pair(l, r));
pool.push_back(l);
pool.push_back(r);
}
// 离散化
sort(pool.begin(), pool.end());
int cnt = unique(pool.begin(), pool.end()) - pool.begin();
for(int i = 0; i < m1; i++) {
A1[i].first = lower_bound(pool.begin(), pool.begin() + cnt, A1[i].first) - pool.begin() + 1;
A1[i].second = lower_bound(pool.begin(), pool.begin() + cnt, A1[i].second) - pool.begin() + 1;
}
for(int i = 0; i < m2; i++) {
A2[i].first = lower_bound(pool.begin(), pool.begin() + cnt, A2[i].first) - pool.begin() + 1;
A2[i].second = lower_bound(pool.begin(), pool.begin() + cnt, A2[i].second) - pool.begin() + 1;
}
// 分别处理
handle(A1, F1);
handle(A2, F2);
for(int i = 1; i <= n; i++) F1[i] += F1[i-1], F2[i] += F2[i-1];
int ans = 0;
for(int i = 0; i <= n; i++) {
ans = max(ans, F1[i] + F2[n-i]);
}
cout << ans << endl;
return 0;
}
一句话题意:
为?位置填充字符,求合法的序列数量。
关键点:
分析:
本题的类型非常好确认,难点在于搞清楚合法的序列的定义,具体的包含了以下几种(两类, 1-3是包围类,4是并排类):
搞清楚这几类之后,我们要的东西就呼之欲出了:
易错点
有的同学考虑区间dp会导致重复的问题,因此我们将所有的合法括号分为两类,上面已经描述过。这两类之间是不重不漏的。其中第二类会出现重复计数的问题,因此我们枚举ASA中,第一个A的位置,并且要求这个A一定是一个包围类的A。则可以避免重复。
#include
using namespace std;
typedef long long ll;
template<class T> bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T> bool chmin(T &a, const T &b) { if (a>b) { a=b; return 1; } return 0; }
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LLINF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 505;
const int MAXM = 2000005;
ll dp[MAXN][MAXN][4], b[MAXN], vis[MAXN];
int n, m, k;
string s;
void DEBUG(int i, int j) {
for(int p = i; p <= j; p++) printf("%c", s[p]);
printf(" (%d, %d): %lld, %lld, %lld, %lld\n", i, j, dp[i][j][0], dp[i][j][1], dp[i][j][2], dp[i][j][3]);
}
int main(){
cin >> n >> m;
cin >> s;
if(m >= 1) for(int i = 0; i < n; i++) {
if(s[i] == '?' || s[i] == '*') dp[i][i][3] = 1;
// DEBUG(i, i);
}
for(int L = 2; L <= n; L++) {
for(int i = 0; i < n && i + L - 1 < n; i++) {
int j = i + L - 1;
// dp[i][j][0] 包围型( )
if(s[i] == '?' && s[j] == ')' || s[i] == '(' && s[j] == '?' || s[i] == '?' && s[j] == '?' || s[i] == '(' && s[j] == ')') {
if(i+1 > j-1) dp[i][j][0] = 1;
else dp[i][j][0] = ( dp[i][j][0] + dp[i+1][j-1][0] + dp[i+1][j-1][1] + dp[i+1][j-1][2] + dp[i+1][j-1][3] ) % MOD;
}
// dp[i][j][0] 并排型
int f = 0;
for(int k = i+1; k < j-1; k++) {
// 枚举前一段的A
if((s[i] == '(' || s[i] == '?') && (s[k] == ')' || s[k] == '?')) {
if(i+1 == k) {
// AB
dp[i][j][0] = ( dp[i][j][0] + dp[k+1][j][0] ) % MOD;
// A + SB
dp[i][j][0] = ( dp[i][j][0] + dp[k+1][j][2] ) % MOD;
}
else {
// AB
dp[i][j][0] = ( dp[i][j][0] + (dp[i+1][k-1][0] + dp[i+1][k-1][3] + dp[i+1][k-1][1] + dp[i+1][k-1][2]) * dp[k+1][j][0] ) % MOD;
// A + SB
dp[i][j][0] = ( dp[i][j][0] + (dp[i+1][k-1][0] + dp[i+1][k-1][3] + dp[i+1][k-1][1] + dp[i+1][k-1][2]) * dp[k+1][j][2] ) % MOD;
}
}
// AS + B 和 A + SB 重复
// dp[i][j][0] = ( dp[i][j][0] + dp[i][k][1] * dp[k+1][j][0] ) % MOD;
}
// dp[i][j][1] AS型
for(int k = j-1; k >= j - m && k > i; k--) {
dp[i][j][1] = ( dp[i][j][1] + dp[i][k][0] * dp[k+1][j][3] ) % MOD;
}
// dp[i][j][2] SA型
for(int k = i+1; k <= i + m && k < j; k ++) {
dp[i][j][2] = ( dp[i][j][2] + dp[i][k-1][3] * dp[k][j][0] ) % MOD;
}
// dp[i][j][3] S型
if(L <= m){
int f = 1;
for(int k = i; k <= j; k++) {
if(s[k] == '(' || s[k] == ')') {
f = 0;break;
}
}
if(f) dp[i][j][3] = 1;
}
// DEBUG(i, j);
}
}
cout << dp[0][n-1][0] << endl;
return 0;
}
一句话题意:
你可以从a序列的两端以任意顺序取数,获得一个回文串。
关键点:
分析
#include
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LL_INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 5e5 + 10;
const int MAXM = 2e6 + 10;
int a[MAXN*2], b[MAXN], p[MAXN][2], vis[MAXN];
char s[MAXN*2];
int n, m, T, ans;
void DFS(int L, int R, int mL, int mR) {
int step = n*2 - (R-L+1) + 1; // 当前选择的是第step个数字。
if(step > n) {
ans = 1;
return ;
}
// Left
if(L < mL) {
if(L < mL -1 && a[L] == a[mL-1]) {
s[step] = 'L';
s[2*n-step + 1] = 'L';
DFS(L+1, R, mL-1, mR);
if(ans) return ;
}
if(R > mR && a[L] == a[mR+1] ) {
s[step] = 'L';
s[2*n-step + 1] = 'R';
DFS(L+1, R, mL, mR+1);
if(ans) return ;
}
}
// Right
if(R > mR) {
if(L < mL && a[R] == a[mL-1]) {
s[step] = 'R';
s[2*n-step + 1] = 'L';
DFS(L, R-1, mL-1, mR);
if(ans) return ;
}
if(R > mR + 1 && a[R] == a[mR+1]) {
s[step] = 'R';
s[2*n-step + 1] = 'R';
DFS(L, R-1, mL, mR+1);
if(ans) return ;
}
}
}
int main(){
cin >> T;
while(T--) {
cin >> n;
memset(p, 0, sizeof(p));
for(int i = 1; i <= 2*n; i++) {
cin >> a[i];
if(p[a[i]][0] == 0) p[a[i]][0] = i;
else p[a[i]][1] = i;
}
ans = 0;
s[1] = 'L';
s[2*n] = 'L';
DFS(2, 2*n, p[a[1]][1], p[a[1]][1]);
if(ans) {
for(int i = 1; i <= 2*n; i++) {
cout << s[i];
}
cout << endl;
continue;
}
ans = 0;
s[1] = 'R';
s[2*n] = 'L';
DFS(1, 2*n-1, p[a[2*n]][0], p[a[2*n]][0]);
if(ans) {
for(int i = 1; i <= 2*n; i++) {
cout << s[i];
}
cout << endl;
continue;
}
cout << -1 << endl;
}
return 0;
}
一个写在脸上的网络流问题。最小割。
就是处理点编号的时候麻烦一点。
板子大概能得65分。其他的TLE,暂时没想到优化。
#include
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LL_INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 5e5 + 10;
const int M = 2e5 + 10;
int S, T;
int n, m, K;
int mw[505][505][2];
struct Edge {
int from,to,nxt, cap,flow;
Edge () {}
Edge(int from,int to,int nxt, int cap,int flow): from(from),to(to),nxt(nxt), cap(cap),flow(flow) {}
}base[MAXN<<2];
int cnt_base, head_base[MAXN];
void init() {
cnt_base = 0;
memset(head_base, -1, sizeof(head_base));
}
void add(int u, int v, int w) {
base[cnt_base] = Edge(u, v, head_base[u], w, 0);
head_base[u] = cnt_base++;
base[cnt_base] = Edge(v, u, head_base[v], w, 0);
head_base[v] = cnt_base++;
}
struct Dinic {
int n,m,s,t, cnt;
Edge edges[MAXN << 2];
int head[MAXN];
bool vis[MAXN];
int d[MAXN];
int cur[MAXN];
void init() {
cnt = cnt_base;
memcpy(head, head_base, sizeof(head_base));
memcpy(edges, base, sizeof(base));
}
void addedge(int from,int to,int cap) {
edges[cnt] = Edge(from,to,head[from], cap,0);
head[from] = cnt++;
edges[cnt] = Edge(to,from,head[to], cap,0);
head[to] = cnt++;
}
bool bfs() {
memset(vis,0,sizeof vis);
queue<int> q;
q.push(s);
vis[s] = 1;
while(!q.empty()) {
int now = q.front(); q.pop();
for(int i = head[now]; ~i; i=edges[i].nxt) {
Edge &e = edges[i];
if(!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[now] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int dfs(int x,int res) {
if(x == t || !res) return res;
int flow = 0,f;
for(int &i = cur[x]; ~i; i=edges[i].nxt) {
Edge &e = edges[i];
if(d[x] + 1 == d[e.to] && (f = dfs(e.to,min(res,e.cap - e.flow))) > 0) {
e.flow += f;
edges[i^1].flow -= f;
flow += f;
res -= f;
if(!res) break;
}
}
return flow;
}
int maxflow(int s,int t) {
this->s = s;
this->t = t;
int flow = 0;
while(bfs()) {
memcpy(cur,head,sizeof head);
flow += dfs(s,INF);
}
return flow;
}
}dinic;
int getV(int p) {
if(p <= m) return p;
if(p <= n+m) return (p-m) * m;
if(p <= n+2*m) return m - (p-n-m) + 1 + (n-1)*m;
return (n - (p-n-2*m)) * m + 1;
}
int main(){
scanf("%d %d %d", &n, &m, &K);
for(int i = 1; i < n; i++) for(int j = 1; j <= m; j++) scanf("%d", &mw[i][j][0]);
for(int i = 1; i <= n; i++) for(int j = 1; j < m; j++) scanf("%d", &mw[i][j][1]);
init();
for(int i = 1; i < n; i++) {
for(int j = 1; j <= m; j++) {
int u = (i-1) * m + j;
int v = i*m + j;
add(u, v, mw[i][j][0]);
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j < m; j++) {
int u = (i-1) * m + j;
int v = (i-1) * m + j + 1;
add(u, v, mw[i][j][1]);
}
}
// 以上是基础图,每次询问需要重置整个图。
while(K--) {
dinic.init();
int k; scanf("%d", &k);
T = n*m + k + 1;
for(int i = 1; i <= k; i++) {
int w, p, y; scanf("%d %d %d", &w, &p, &y);
int u = n*m + i;
int v = getV(p);
dinic.addedge(u, v, w);
if(y) dinic.addedge(u, T, INF);
else dinic.addedge(0, u, INF);
}
printf("%d\n", dinic.maxflow(0, T));
}
return 0;
}