暑假集训3
A. 数列
求解同余方程的模板题,重要的是别把解法忘了,就只能乱搞了……
求解 ax + by = c
1. 解 ax + by = gcd(a, b); gcd(a, b) = ans;
2. b = b / ans; c = c / ans; if(b < 0) b = -b;
3. x = (x * c % b + b) % b; y = (c - a * x) / b;
如果求的是abs(x)+abs(y)最小,答案是:
ans = min(abs(x)+abs(y), abs(x-b)+abs(y+a))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 3; const ll mod = 1e9 + 7; int n, a, b, d[maxn]; ll cat, ther, ine, gcd, x, y; bool fl; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } ll ex_gcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll d = ex_gcd(b, a%b, x, y); ll tmp = x; x = y; y = tmp - a / b * y; return d; } int main() { //freopen("array.in", "r", stdin); n = read(); a = read(); b = read(); a = abs(a), b = abs(b); for(int i=1; i<=n; i++) { d[i] = read(); if(d[i] < 0) d[i] = -d[i]; } if(a > b) swap(a, b); gcd = ex_gcd(a, b, x, y); a /= gcd; b /= gcd; x = (x % b + b) % b; for(int i=1; i<=n; i++) { if(d[i] % gcd) { printf("-1"); exit(0); } d[i] /= gcd; cat = (x*d[i]%b+b)%b, ther = (d[i]-a*cat)/b; ine += min(abs(cat)+abs(ther), abs(cat-b)+abs(ther+a)); } printf("%lld\n", ine); return 0; }
B. 数对
错误思路:赛时不会,于是想到了暴力,从1到n枚举必须选的点对,然后以它为基准在整个区间范围内找a比minb小的没有被选过的里b最大的,再继续以它为基准在整个区间范围内找b比maxa大的没有被选过的里a最小的,自以为很对,但是“没有被选过”这个条件就说明了枚举的时间也造成了影响,发现这个问题之后我把两个查找函数换了个位置再找一遍但他还是不对,没时间改了样例都没过,交上去水了10分。
其实,这是一个dp:f[i][j]表示前i个数里max_a==j的最多价值——
由于a和b同时被塞进了数组里,虽然有unique,但线段树维护的相当于还是两倍空间,小心RE。
caorong的总结写的很好%%%
线段树优化DP
给你三元组(ai,bi,wi),要求你给出一种排列顺序,对于任意的i<j,ai<=bj,而且被选集合的wi之和最大。(n<=le5)
最优解的实现有3个步骤难点
(1)排列顺序:我们假设一种情况,如果i和j都要选,那么i必须在j前面的限制是什么,即ai<=bj,aj>bi,化简合并,
体现在结构体的排序里就是ai+bi<ai+bj
(2)选择:dp[i][j]表示前i个数里max_a=j的最多价值,
枚举到一个i,当bi>ai,只能从dp[i-1][1~ai]转移(第二维限制);
当ai>=bi,从dp[i-1][1~bi]转移(条件);
可以更新的决策集合:当ai<bi,可以更新dp[i][ai~bi]
当ai>bi,只能更新dp[i][ai](用1~bi的)。(为什么不能更新bi~ai?因为定义啊max_a怎么能越取越小呢)
发现都是区间上的操作,用线段树维护。支持区间加减,维护单点最值
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 5; const int INF = 0x7fffffff; const ll mod = 1e9 + 7; int n, mp[maxn<<1], uni; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { int a, b; ll w; bool operator < (const node &T) const { return a + b < T.a + T.b; } }e[maxn]; #define lson (rt<<1) #define rson (rt<<1|1) struct Tree { ll max, lazy; }tree[maxn<<2]; void pushup(int rt) { tree[rt].max = max(tree[lson].max, tree[rson].max); } void pushdown(int rt) { if(tree[rt].lazy) { ll lz = tree[rt].lazy; tree[rt].lazy = 0; tree[lson].lazy += lz; tree[rson].lazy += lz; tree[lson].max += lz; tree[rson].max += lz; } } void update(int rt, int l, int r, int L, int R, ll k) { if(L <= l && r <= R) { tree[rt].lazy += k; tree[rt].max += k; return; } pushdown(rt); int mid = (l + r) >> 1; if(L <= mid) update(lson, l, mid, L, R, k); if(R > mid) update(rson, mid+1, r, L, R, k); pushup(rt); } ll query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) { return tree[rt].max; } pushdown(rt); int mid = (l + r) >> 1; ll ans = 0; if(L <= mid) ans = max(ans, query(lson, l, mid, L, R)); if(R > mid) ans = max(ans, query(rson, mid+1, r, L, R)); return ans; } int main() { n = read(); for(int i=1; i<=n; i++) { e[i].a = read(); e[i].b = read(); e[i].w = read(); mp[(i<<1)-1] = e[i].a; mp[i<<1] = e[i].b; } sort(e+1, e+1+n); sort(mp+1, mp+1+(n<<1)); uni = unique(mp+1, mp+(n<<1)+1)-mp-1; for(int i=1; i<=n; i++) { int ar = lower_bound(mp+1, mp+uni+1, e[i].a)-mp; int br = lower_bound(mp+1, mp+uni+1, e[i].b)-mp; int mi = min(ar, br); ll t1 = query(1, 1, uni, 1, mi); ll t2 = query(1, 1, uni, ar, ar); if(ar < br) update(1, 1, uni, ar+1, br, e[i].w); update(1, 1, uni, ar, ar, -t2); update(1, 1, uni, ar, ar, max(t2, t1+e[i].w)); } printf("%lld\n", query(1, 1, uni, 1, uni)); return 0; }
C. 最小距离
暴力15 eps,要注意的就是千万别换行!!!Cat又大E了……
以所有特殊点为起点跑多源最短路,记录每个点是由哪个起点更新的,依然是每个点只被更新一次,这样每个点就有了明确的归属。跑完最短路后枚举每条边,连接两个区域的边可以给它的两端贡献答案。
就是一旦走出了自己的“故乡”到了另一个点的“势力范围”内,就别出去了,直接走到源点得了,被谁拓展到谁就是最近的。
dij的神奇应用++;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 5; const int INF = 0x7fffffff; const ll mod = 1e9 + 7; int n, m, p, belong[maxn], sp[maxn]; ll ans[maxn], dis[maxn]; bool vis[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { int fr, to, next; ll w; }a[maxn<<1]; int head[maxn], len; void add(int x, int y, ll w) { a[++len].to = y; a[len].next = head[x]; a[len].w = w; a[len].fr = x; head[x] = len; } struct SAT { int id, bl; ll dis; SAT(){} SAT(int a, int b, ll c) { id = a, bl = b, dis = c; } bool operator < (const SAT &T) const { return dis > T.dis;//优先队列里排序和sort相反 } }; priority_queue<SAT> q; void dij() { for(int i=1; i<=n; i++) { dis[i] = 1e17; } for(int i=1; i<=p; i++) { belong[sp[i]] = sp[i]; q.push(SAT(sp[i], sp[i], 0)); dis[sp[i]] = 0; } while(!q.empty()) { SAT u = q.top(); vis[u.id] = 1; for(int i=head[u.id]; i; i=a[i].next) { int v = a[i].to; if(dis[v] > dis[u.id] + a[i].w) { dis[v] = dis[u.id] + a[i].w; belong[v] = u.bl; q.push(SAT(v, belong[v], dis[v])); } } while(!q.empty() && vis[q.top().id]) q.pop(); } } int main() { //freopen("distance.in", "r", stdin); n = read(); m = read(); p = read(); for(int i=1; i<=p; i++) { sp[i] = read(); } for(int i=1; i<=m; i++) { int x = read(), y = read(), w = read(); add(x, y, w); add(y, x, w); } for(int i=1; i<=n; i++) ans[i] = 1e17; dij(); for(int i=1; i<=len; i++) { int u = a[i].fr, v = a[i].to; if(belong[u] != belong[v]) { ans[belong[u]] = min(ans[belong[u]], a[i].w+dis[u]+dis[v]); ans[belong[v]] = min(ans[belong[v]], a[i].w+dis[u]+dis[v]); } } for(int i=1; i<=p; i++) { printf("%lld ", ans[sp[i]]); } return 0; }
D. 真相
n<=10直接2^n枚举每个人说话的真假性,有一个合法的就好,实现的话可以状压一下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 12; const int INF = 0x7fffffff; const ll mod = 1e9 + 7; int T, n, Max; char s[3]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { bool op, t; int tot; }p[12]; bool check(int mx) { //printf("mx = %d\n", mx); int tot = -1, num = 0, cnt = 0; vector<int> nt; for(int i=n-1; i>=1; i--) { cnt++; //printf("cnt = %d\n", cnt); if((mx>>i)&1) { //printf("%d is true\n", cnt); num++; if(p[cnt].op == 1) { if(tot == -1) tot = p[cnt].tot; else if(tot != p[cnt].tot) return 0; } else if(p[cnt].t == 1) { if(((mx>>(i-1))&1) == 0) return 0; } else { if(((mx>>(i-1))&1) == 1) return 0; } } else { //printf("%d is false\n", cnt); if(p[cnt].op == 1) { nt.push_back(p[cnt].tot); } else if(p[cnt].t == 1) { if(((mx>>(i-1))&1) == 1) return 0; } else { if(((mx>>(i-1))&1) == 0) return 0; } } } cnt++; if(mx & 1) { num++; if(p[cnt].op == 1) { if(tot == -1) tot = p[cnt].tot; else if(tot != p[cnt].tot) return 0; } else if(p[cnt].t == 1) { if(((mx>>(n-1))&1) == 0) return 0; } else if(p[cnt].t == 0) { if(((mx>>(n-1))&1) == 1) return 0; } } else { //printf("%d is false\n", cnt); if(p[cnt].op == 1) { nt.push_back(p[cnt].tot); } else if(p[cnt].t == 1) { if(((mx>>(n-1))&1) == 1) return 0; } else if(p[cnt].t == 0) { if(((mx>>(n-1))&1) == 0) return 0; } } if(tot != -1 && num != tot) return 0; int sz = nt.size(); for(int i=0; i<sz; i++) { if(num == nt[i]) return 0; } return 1; } int main() { T = read(); while(T--) { n = read(); for(int i=1; i<=n; i++) { scanf("%s", s); if(s[0] == '$') { p[i].op = 1; p[i].tot = read(); } else if(s[0] == '+') { p[i].op = 0; p[i].t = 1; } else { p[i].op = 0; p[i].t = 0; } } /*for(int i=1; i<=n; i++) { printf("%d %d %d\n", p[i].op, p[i].t, p[i].tot); }*/ Max = (1<<n)-1; bool f = 0; for(int i=0; i<=Max; i++) { //printf("New check: \n"); if(check(i)) { f = 1; break; } } if(f) printf("consistent\n"); else printf("inconsistent\n"); } return 0; }
正解的代码和官方正解的语句几乎一一对应,关于代码解释和我自己的理解详见注释——
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 6; const int INF = 0x7fffffff; const ll mod = 1e9 + 7; int tot, q[maxn], n, top, siz[maxn][2], sum, T; bool flag; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } bool ismychar(char c) { return c == '+' || c == '-' || c == '$'; } struct node { char op; int a; void init() { while(!ismychar(op = getchar())); if(op == '$') scanf("%d", &a); } }tmp[maxn], num[maxn]; bool check() { for(int i=1; i<=top; i++) { if(sum == q[i]) return 0;//直接默认A说的都是假话 //呢在默认A说假话的情况下有任何一个A说了真话都不合法 } return 1; } //对于第一个说第一种话的A,如果她的话真假性被确定了,在他之前一直到上一个说第一种话的人B //这段内所有人说话的真假性都被确定了 //预处理出A说真话和假话时该段分别说真话和假话的人数 void doit() { int cnt1 = 0, cnt2 = 0; for(int i=1; i<=n; i++) { cnt1++;//说真话的人数?或者,真话假话都一样,说同类话的人数? //以A为终点,A一定要说真话,所以cnt1是说真话的人数,但是为了让A说真话 //cnt1的组成要不断调整 switch(num[i].op) { case '-': tot = cnt1; cnt1 = cnt2; cnt2 = tot;//1,2互换 break; case '$': tot = num[i].a; //把每一种新的“真话人数要求”放进栈里备用 //但也不一定是新的,如果cnt1是0的话 if(siz[tot][1] == 0) q[++top] = tot; //为什么要求相同可以合并? siz[tot][0] += cnt2;//如果A说假话 siz[tot][1] += cnt1;//如果A说真话 sum += cnt2;//枚举时默认A说假话? cnt1 = cnt2 = 0; break; } } if(!flag)//所有人说的都是第一种话 { //只要有一个人说目标真话人数是0,siz[0][1]就>0,没有人说真话就不合法 //因为提出这个目标的人说了真话 //否则所有人都说假话就是一种特殊解 if(!siz[0][1]) return; //目标真话人数是q[i]时 //就是有多少人目标相同,就是当前目标是真话的实际真话人数 for(int i=1; i<=top; i++) { if(q[i] == siz[q[i]][1]) return; } printf("in"); return; } if(check()) return; //边判断便清空,还真的是当栈用了 while(top) { int now = q[top--];//A全说假话行不通,就让他说真话 //所以要求恰好对上 //这也就是要求相同可以合并的原因——A的真假性相同,要加一起加且 //要加只加一个,因为其它要求不同的A一定是假的 if(siz[now][1]+sum-siz[now][0] == now) return; } printf("in"); } int main() { T = read(); while(T--) { n = read(); flag = sum = tot = top = 0; while(tot + 1 <= n) { tmp[++tot].init(); if(tmp[tot].op == '$') break; else flag = 1; } //如果没有人说第一种话 if(tot == n && tmp[tot].op != '$') { tot = 0; for(int i=1; i<=n; i++) { tot += (tmp[i].op == '-'); } //假话信号遇到真话信号向后传递,遇到假话信号相互抵消 //因为假话信号和‘-’得到真话推论,所以所有人都在说假话这种情况合法 //当且仅当所有人都是‘+’,否则要想合法一定有一个人说了真话 //但是如果假话信号为奇数,就一定会把假话推论传递到说真话的人身上 //所以矛盾 //所有人都是‘+’时tot == 0,满足 if(tot & 1) printf("in"); printf("consistent\n"); continue; } //以说第一种话的人为终点 for(int i=tot+1; i<=n; i++) { num[i-tot].init(); flag |= (num[i-tot].op != '$'); } for(int i=1; i<=tot; i++) { num[n-tot+i] = tmp[i]; } memset(siz, 0, sizeof(siz)); doit(); printf("consistent\n"); } return 0; }
时光花火,水月星辰