马克飞象莫名其妙无法同步到印象笔记,现在这里保存一下吧。
@(ACM集训)
无向图,给定边及边权重,任意两点之间都有一条唯一的道路,道路上每个点只能出现一次。给定询问,求询问的结点之间的距离。
路上每个点只能出现一次,可以转化成有根树,问题也即为求最近公共祖先问题~~ 这里每条边加上了距离,求出LCA后,用 u、v 的距离根的距离和减去 2 倍的根到最近公共祖先的距离即可,然后就是tarjan LCA 模板题了。
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
#define fuck cout<<"fuck"<<endl;
const int maxn = 40005, maxq = 205;
typedef pair<int, int>p;
vector<p>G[maxn];
#define fi first
#define se second
int pa[maxn];
bool vis[maxn];
bool in[maxn];
int ance[maxn];
int dist[maxn];
int n, tot;
int head[maxn];
int ans[maxn];
struct Query{int to, next, index;};
Query query[maxq * 2];
void add_query(int u, int v, int index)
{
query[tot].to = v;
query[tot].index = index;
query[tot].next = head[u];
head[u] = tot++;
query[tot].to = u;
query[tot].index = index;
query[tot].next = head[v];
head[v] = tot++;
}
int _find(int x)
{
if(pa[x] != x) return pa[x] = _find(pa[x]);
return x;
}
void unite(int x, int y)
{
int rx = _find(x), ry = _find(y);
if(rx == ry) return;
pa[rx] = ry;
}
void init()
{
tot = 0;
for(int i = 1; i <= n; i++){
G[i].clear();
pa[i] = i;
}
mem(ance, 0);
mem(vis, false);
mem(head, -1);
mem(dist, 0);
mem(in, false);
mem(ans, 0);
}
void LCA(int u)
{
ance[u] = u;
vis[u] = true;
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].fi;
if(vis[v]) continue;
dist[v] = dist[u] + G[u][i].se;
LCA(v);
unite(u, v);
ance[_find(u)] = u;
}
for(int i = head[u]; i != -1; i = query[i].next){
int v = query[i].to;
if(vis[v]) ans[query[i].index] = dist[u] + dist[v] - 2 * dist[ance[_find(u)]];
}
}
int main(void)
{
int u, v, k;
int a, b, c;
int Q;
int T;scanf("%d", &T);
while(T--){
init();
scanf("%d%d", &n, &Q);
for(int i = 0; i < n - 1; i++){
scanf("%d%d%d",&a, &b, &c);
G[a].push_back(p(b, c));
G[b].push_back(p(a, c));
}
for(int i = 0; i < Q; i++){
scanf("%d%d",&u,&v);
add_query(u, v, i);
}
dist[1] = 0;
LCA(1);
for(int i = 0; i <Q; i++){
printf("%d\n", ans[i]);
}
}
return 0;
}
经典过河问题,一个船只能运两个人,每个人过河时间不同,船来回往返使得 n 个人全部通过,问如何安排使得总时间最小。
小学奥数题的感觉。。
决策如下:
1. n<=2 ,直接过河。
2. n=3 ,让最快的往返一次。
3. n>=4 ,要么最快的来回往返,要么最慢的和次慢的过去了就不要再回来。那么把最慢的和次慢的两个人送过去就有两种方法:
- 最小的来回往返,一次送最慢,回来,再送次慢。
- 最小和次小的一起送(要保证最慢的和次慢的过去以后还有人能把船运回来),最小的和次小的先一起过去,然后最小的回来,然后最慢的和次慢的一起过去,次小的回来。
取两种方法中的最小值即可。依次循环按上述策略处理。
/************************************************************************* > File Name: > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
typedef pair<int, int>p;
typedef long long ll;
const int maxn = 1e3 + 5;
int a[maxn];
int main (void)
{
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i = 0; i < n; i++) cin>>a[i];
sort(a, a + n);
int ans = 0;
while(n){
if(n == 1){
ans += a[0];
break;
} else if(n == 2){
ans += a[1];
break;
}else if(n == 3){
ans += a[0] + a[1] + a[2];
break;
}else{
int t1 = a[0] * 2 + a[n - 1] + a[n - 2];
int t2 = a[1] * 2 + a[0] + a[n - 1];
n -= 2;
ans += min(t1, t2);
}
}
cout<<ans<<endl;
}
return 0;
}
给定 n 个蚂蚁的初始位置及方向,两只蚂蚁相遇后同时掉头,速度均为 1m/s ,问 T 时间后,各个蚂蚁的位置。
可以将问题看成,两只蚂蚁相遇后交换速度和方向,那么本质就相当于相遇后直接穿过对方,继续原方向行走,而由于两只蚂蚁相遇后直接掉头,所以最后 所有蚂蚁的相对顺序保持不变,这样我们算出所有终态,排个序,按照对应初始位置的顺序输出即可。注意判断是否掉下去的优先级要比位置是否相同的高!
/************************************************************************* > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 1e4 + 5, oo = 0x3f3f3f3f;
struct NODE{int x; int dir; int id;};
NODE node[maxn], nnode[maxn];
map<int, int>MAP;
bool cmp(NODE a, NODE b)
{
return a.x < b.x;
}
int main (void)
{
int N;sa(N);
for(int kas = 1; kas <= N; kas++){
int L, T, n;sa(L);sa(T);sa(n);
int x;
for(int i = 1; i <= n; i++){
sa(x);getchar();
if(getchar() == 'L'){
node[i] = (NODE){x, 1, i};
nnode[i] = (NODE){x - T, 1, 0};
}else{
node[i] = (NODE){x, 2, i};
nnode[i] = (NODE){x + T, 2, 0};
}
}
sort(node + 1, node + n + 1, cmp);
for(int i = 1; i <= n; i++){
MAP[node[i].id] = i;
}
sort(nnode + 1, nnode + n + 1, cmp);
for(int i = 1; i <= n; i++){
if(nnode[i].x < 0 || nnode[i].x > L)
nnode[i].dir = -1;
else if(i < n && nnode[i].x == nnode[i + 1].x){
nnode[i].dir = 0;
nnode[i + 1].dir = 0;
}
}
printf("Case #%d:\n", kas);
for(int j = 1; j <= n; j++){
int i = MAP[j];
if(nnode[i].dir == -1) {
cout<<"Fell off"<<endl;
continue;
}
cout<<nnode[i].x<<' ';
if(nnode[i].dir == 1){
cout<<'L'<<endl;
}else if(nnode[i].dir == 2){
cout<<'R'<<endl;
}else if(nnode[i].dir == 0){
cout<<"Turning"<<endl;
}
}
cout<<endl;
/*for(int i = 1; i <= n; i++){ cout<<nnode[i].id<<' '<<nnode[i].x<<' '<<nnode[i].dir<<endl; }*/
}
return 0;
}
给定01序列,翻转一位其左右相邻两位也会被翻转,问使序列全部变为0的最小翻转次数。
简单线性排列,由于后面是否翻转由前面的决定,所以我们枚举第一个元素的状态,然后从头扫一遍即可。
/************************************************************************* > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 20 + 5, oo = 0x3f3f3f3f;
int f[maxn], a[maxn];
int main (void)
{
for(int i = 0; i < 20; i++){
cin>>a[i];
}
memset(f, 0, sizeof(f));
int cnt = 0;
for(int i = 1; i < 20; i++){
if((a[i - 1] + f[i - 1]) & 1){
cnt++;
f[i]++;
f[i + 1]++;
}
}
memset(f, 0, sizeof(f));
int cnt2 = 1;
f[0] = f[1] = 1;
for(int i = 1; i < 20; i++){
if((a[i - 1] + f[i - 1]) & 1){
cnt2++;
f[i]++;
f[i + 1]++;
}
}
cout<<min(cnt, cnt2)<<endl;
return 0;
}
有 m∗n 个黑白块,黑块的背面是白块,白块背面是黑块,一头牛踩一块,则这个块的上下左右的方块都会转动,问至少踩多少块,才会使所有块都变成白色?
一个块的转动会影响其他块的状态,这里不是简单的线性排列,不能只踩黑块。
首先根据字典序,我们可以对第一排从 00…00 到 11..11 进行考虑(1表示踩),再后续一排一排的考虑。因为踩一块四周的块都会转动,所以必须规定个踩的原则,发现对于某块来说,他一旦改变上方块的状态,那个块就再也不会改变了,而其他块还有他的下一列改变他的机会(如果存在),所以就以上一行块为原则,如果上方为黑,则踩。最后判断最后一行是否全白。
字典序,因为他说了是把整个排列当做字符串的字典序,所以肯定是越前面的越小越好,而且从第一个例子中也能看出来。
#include<iostream>
#include<cstring>
using namespace std;
#define mem(s,a) memset(s,a,sizeof(s));
int m, n;
const int maxn = 25, INF = 0x3fffffff;
int x[5]={-1,0,0,0,1};
int y[5] = {0,1,0,-1,0};
int s[maxn][maxn], a[maxn][maxn], r[maxn][maxn], ans[maxn][maxn];
int cal()
{
int cnt = 0;
for(int i = 2; i <= m; i++){
for(int j = 1; j <= n; j++){
if((a[i-1][j] + r[i-1][j]) % 2 == 1) {
cnt++;
s[i][j] = 1;
}
for(int k = 0; k < 5; k++){
r[i + x[k]][j + y[k]] += s[i][j];
}
}
}
for(int i =1; i <= n; i++){
if((r[m][i] + a[m][i]) % 2 == 1) return -1;
}
return cnt;
}
int main (void)
{
cin>>m>>n;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
cin>>a[i][j];
}
}
int res = INF;
for(int i = 0; i <1<<n; i++){
mem(s,0);mem(r,0);
int t = 0;
for(int j =n; j >= 1; j--){
s[1][j] = i>>j&1;
for(int k = 0; k < 5; k++){
r[1 + x[k]][j + y[k]] += s[1][j];
}
t += s[1][j];
}
int tm = cal();
if(tm> = 0 && t + tm < res){
res = t + tm;
memcpy(ans,s,sizeof(s));
}
}
if(res == INF) {
cout<<"IMPOSSIBLE"<<endl;
return 0;
}
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
cout<<ans[i][j];
if(j != n) cout<<' ';
else cout<<endl;
}
}
return 0;
}
给定 P 页书及每页的知识点,最少读多少个连续的页使得书中所有知识点都被覆盖。
最少,连续->双指针
挑战例题,双指针入门题
#include<cstdio>
#include<cstring>
#include<set>
#include<map>
#include<iostream>
using namespace std;
const int maxn = 1e6 + 5;
#define sa(n) scanf("%d", &n)
int a[maxn];
map<int, int>cnt;
int main (void)
{
int P;sa(P);
set<int>s;
for(int i = 0; i < P; i++){
sa(a[i]);
s.insert(a[i]);
}
int n = s.size();
int sz = 0;
int r;
for(r = 0; r < P; r++){
if(!cnt[a[r]]) sz++;
cnt[a[r]]++;
if(sz == n) break;
}
int l = 0;
int ans = r - l + 1;
while(r < P){
if(cnt[a[l]] == 1){
while(r < P && a[r] != a[l]) {
r++;
cnt[a[r]]++;
}
if(r == P) break;
}
cnt[a[l]]--;
ans = min(ans, r - l);
l++;
}
cout<<ans<<endl;
return 0;
}
给定序列及target,求一个子序列使得该序列和的绝对值最接近target。
挑战上的习题
求子区间和问题,先预处理个前缀和。
前缀和有正有负且不单调。因为题目中说的是区间和的绝对值最接近target,所以我们将前缀和排个序,这样保证求出的值为正即为相应区间求出绝对值之后的结果,整个序列单调,这样舍和取的条件非常明确,直接在上面二指针以获取值最接近target的即可。
观察题目中的性质区间和的绝对值,巧妙的化简问题。
最初用的 longlong ,姿势不太对一直CE,改回 int 就过了。。不是很理解。。
还有注意结果非空,即 l!=r
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn = 1e5 + 5, oo = 0x3f3f3f3f;
int a[maxn];
typedef pair<int, int>P;
P p[maxn];
int n, k;
int ans;
int L, R;
void gao(int t)
{
int l = 0, r = 1;
ans = oo;
int res = 0;
while(l <= n && r <= n){
if(abs(p[r].first - p[l].first- t)< ans){
ans = abs(p[r].first - p[l].first - t);
L = p[l].second;
R = p[r].second;
res = abs(p[r].first - p[l].first);
}
if(p[r].first - p[l].first < t) r++;
else if(p[r].first - p[l].first > t) l++;
else break;
if(l == r) r++;
}
cout<<res<<' '<<min(L, R) + 1<<' '<<max(L, R)<<endl;
}
int main (void)
{
while(~scanf("%d%d", &n, &k) && (n + k)){
int sum = 0;
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
sum += a[i];
p[i] = P(sum, i);
}
p[0] = P(0, 0);
sort(p, p + n + 1);
int t;
for(int i = 0; i < k; i++){
scanf("%d", &t);
gao(t);
}
}
return 0;
}
用 n 种钱币组成 k 元,问可以再用组成这 m 元的钱币组成多少种钱数?
可以把题意理解为向两个背包里面装东西,一个最后组成 m ,另一个最后组成我们待求的 x ,前提是放入第二个背包中的东西必须放入第一个背包中,也就是 m 必须包含 x 的钱币。
那么有 dp[i][j][k]:=前i个钱币,组成了j,其中的钱币还可以组成k的情况是否存在
状态转移方程就很好写了。
滚动数组可以优化。。但是只有500,我还是无脑的直接搞了。。
听 lcy 大神说过 dp 为 bool 的时候还可以优化。。我还是无脑的直接搞了。。
#include<cstdio>
#include<cstring>
#include<set>
#include<iostream>
using namespace std;
const int maxn = 5e2 + 5;
bool dp[maxn][maxn][maxn];
int a[maxn];
int main (void)
{
int n, m;scanf("%d%d",&n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
}
memset(dp, false, sizeof(dp));
dp[0][0][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = m; j >= 0; j--){
for(int k = j; k >= 0; k--){
dp[i][j][k] |= dp[i - 1][j][k];
if(j - a[i] >= k) dp[i][j][k] |= dp[i - 1][j - a[i]][k];
if(k >= a[i]){
dp[i][j][k] |= dp[i - 1][j - a[i]][k - a[i]];
}
}
}
}
set<int>v;
for(int j = 0; j <= m; j++){
if(dp[n][m][j]) v.insert(j);
}
printf("%d\n", v.size());
set<int>:: iterator s;
for(s = v.begin(); s != v.end(); s++){
printf("%d ", *s);
}
return 0;
}
一个软件有 s 个子系统,会产生 n 种bug,在一个系统中发现一个bug的概率保持不变,问发现 n 种bug, s 子系统都发现bug的天数的期望。
有四种状态转移:
1. 发现的bug在已经发现的 i 个分类和 j 个系统中。概率为 (i∗j)/(n∗s)
2. 发现的bug在已经发现的 i 个分类,但不属于已有的 j 个系统中。概率为 i∗(n−j)/(n∗s)
3. 发现的bug不在已有的 i 个分类中,但在已有的 j 个系统中。概率 (n−i)∗j/(n∗s)
4. 发现的bug不属于已有的 i 个分类也不属于已有的 j 个系统,概率 (n−i)∗(s−j)/(n∗s)
状态转移方程:
dp[i][j]=1+(i∗j)/(n∗s)∗dp[i][j]+i∗(n−j)/(n∗s)∗dp[i+1][j]+(n−i)∗j∗dp[i+1][j]+(n−i)∗(s−j)/(n∗s)∗dp[i+1][j+1]
然后整理一下即可。
/************************************************************************ > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 1e3 + 5, oo = 0x3f3f3f3f;
double dp[maxn][maxn];
int main (void)
{
int n, s;
while(~scanf("%d%d", &n, &s)){
memset(dp, 0, sizeof(dp));
for(int i = n; i >= 0; i--){
for(int j = s; j >= 0; j--){
if(i == n && j == s){
continue;
}
double a4 = 1.0 * n * s / (double)(n * s - i * j);
double a1 = 1.0 * (n - i) * (s - j) / (double) (n * s - i * j);
double a2 = 1.0 * (n - i) * j / (double)(n * s - i * j);
double a3 = 1.0 * i * (s - j) / (double) (n * s - i * j);
dp[i][j] = a4 + dp[i + 1][j + 1] * a1 + dp[i + 1][j] * a2 + dp[i][j + 1] * a3;
}
}
printf("%.4f\n", dp[0][0]);
}
return 0;
}
给定 n 个顶点,建一个围墙围住所有点,并且与所有点的距离至少为 L ,求墙最小的长度。
首先求个凸包,将凸包的边平移 L 个长度即组成墙的一部分,为了最小化墙的长度,我们用半径为 L 的圆弧将凸包平移后的边平滑的连接起来。
我们可以发现每个圆弧与其对应的凸包内角互补,凸边形内角和 180∗(n−2) ,凸边形 n 个内角及对应的圆弧和 180∗n ,两者相减即为圆弧的角度和 360 度,即圆弧正好组成一个半径为 L 的大圆。
所以我们只要求个凸包周长+圆的周长即可。
/************************************************************************* > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
const int maxn = 1e3 + 5;
const double Pi = acos(-1.0);
struct Point
{
int x, y;
Point(){}
Point(int _x,int _y){
x = _x; y = _y;
}
Point operator -(const Point &b)const{
return Point(x - b.x, y - b.y);
}
int operator *(const Point &b)const{
return x * b.x + y * b.y;
}
int operator ^(const Point &b)const{
return x * b.y - y * b.x;
}
};
Point node[maxn], Stack[maxn];
inline double dist(Point p1, Point p2)
{
return sqrt((p2 - p1) * (p2 - p1));
}
int dot(Point p0, Point p1, Point p2)
{
return (p1 - p0) ^ (p2 - p0);
}
int top;
bool cmp(Point a, Point b)
{
if(a.x == b.x) return a.y < b.y;
else return a.x < b.x;
}
void convexHull(int n)
{
sort(node, node + n, cmp);
top = -1;
for(int i = 0; i < n; i++){
while(top > 0 && dot(Stack[top - 1], Stack[top], node[i]) <= 0) top--;
Stack[++top] = node[i];
}
int ttop = top;
for(int i = n - 2; i >= 0; i--){
while(top > ttop && dot(Stack[top - 1], Stack[top], node[i]) <= 0) top--;
Stack[++top] = node[i];
}
}
int main (void)
{
int N, L;sa(N);sa(L);
for(int i = 0; i < N; i++){
sa(node[i].x);sa(node[i].y);
}
convexHull(N);
double ans = 0;
for(int i = 0; i < top; i++){
ans += dist(Stack[i], Stack[i + 1]);
}
ans += 2 * Pi * L;
printf("%.f\n", ans);
return 0;
}
给定序列,由’(”)”[”’]”四个字符组成的字符串,求子序列最大匹配个数。
设 dp[i][j] 表示区间 [i,j] 之间的最大匹配数。
如果 s[i] 与 s[j] 匹配,那么dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + 2);
枚举区间长度,更新对应长度的区间的 dp[i][j] ,再将区间合并更新子结构即可。
/************************************************************************* > File Name: Q.cpp > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%lld", &(n))
typedef long long ll;
typedef pair<int, int>p;
const int maxn = 1e2 + 5;
int dp[maxn][maxn];
int main (void)
{
string s;
while(cin>>s){
if(s == "end") break;
memset(dp, 0, sizeof(dp));
for(int len = 2; len <= s.length(); len++){
for(int j = 0, k = len - 1; j < k - len + 2 && k < s.length(); j++, k++){
if((s[j] == '('&& s[k] == ')') || (s[j] == '[' && s[k] == ']'))
dp[j][k] = max(dp[j][k], dp[j + 1][k - 1] + 2);
for(int i = j + 1; i < k; i++)
dp[j][k] = max(dp[j][k], dp[j][i] + dp[i][k]);
}
}
cout<<dp[0][s.length() - 1]<<endl;
}
return 0;
}
给定剪刀、石头、布分别的人数,两种物种相遇,输的一方少一个人,问最后只剩下仅一种生物的概率。
概率 dp , maxn 最大只有100,容易想到状态 dp[i][j][k]:=第一种剩i人,第二种剩j人,第三张剩k人的概率 ,然后乘上相遇的概率,转移一下就好。
!!!最初计算相遇概率的时候除以的是从剩下所有人中选择两个人的方案数,其实不对,因为只有不同物种相遇才能使当前状态发生改变,所以需要进行归一化处理,即直接除以 i∗j+j∗k+k∗i 。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn = 1e2 + 5, oo = 0x3f3f3f3f;
double dp[maxn][maxn][maxn];
int main (void)
{
int r, s, p;cin>>r>>s>>p;
dp[r][s][p] = 1;
for(int i = r; i >= 0; i--){
for(int j = s; j >= 0; j--){
for(int k = p; k >= 0; k--){
if(i == 0 && j == 0 || i == 0 && k == 0|| j == 0 && k == 0) continue;
if(j >= 1) dp[i][j - 1][k] += dp[i][j][k] * i * j / (double)(i * j + j * k + i * k) ;
if(i >= 1) dp[i - 1][j][k] += dp[i][j][k] * i * k / (double)(i * j + j * k + i * k) ;
if(k >= 1) dp[i][j][k - 1] += dp[i][j][k] * j * k/ (double)(i * j + j * k + i * k) ;
}
}
}
double ans = 0;
for(int i = 1; i <= r; i++){
ans += dp[i][0][0];
}
printf("%.12f ", ans);
ans = 0;
for(int i = 1; i <= s; i++){
ans += dp[0][i][0];
}
printf("%.12f ", ans);
ans = 0;
for(int i = 1; i <= p; i++){
ans += dp[0][0][i];
}
printf("%.12f\n", ans);
return 0;
}
给定序列,若干查询,每个查询给定区间和 t ,输出区间内任意一个不等于 t 的元素的位置。
最初没看样例直接钦定输出每个不等于 t 的元素位置,结果怎么想都是 n2 复杂度的,后来看了样例才发现是输出任意一个。。
对于一个区间,如果区间最大值和最小值相等,那么该区间元素值全部相同,那么我们维护区间的最大最小值,然后判断是否均等于 t ,若不等,输出最大值或最小值的位置即可,若相等, 则该区间所有元素值均等于 t 。
区间最大最小值用线段树维护,最初使用map来保存最大最小值所在的位置,结果TLE,改成数组就过了,就是内存难看了一点。。
感觉自己姿势怪怪的上网搜了一发标程:设 dp[i] 表示不等于 a[i] 的最大的元素下标,这样每次看 t 是否等于区间最右端的值,若是,则判断 dp[t] 是否在区间内,若不是,则最右端的值即为答案。非常巧妙。
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
#define sa(n) scanf("%d", &(n))
const int maxn = 6e5 + 5, maxm = 1e6 + 5, oo = 0x3f3f3f3f;
struct Node{int l;int r;int a; int b;int pa;int pb;}Tree[maxn];
int a[maxn];
int ans[maxm];
void build(int i, int l, int r)
{
Tree[i].l = l;
Tree[i].r = r;
Tree[i].a = 0;
Tree[i].b = oo;
if(l == r) {
Tree[i].pa = Tree[i].pb = l;
return;
}
int mid = l + r >> 1;
build(i << 1, l, mid);
build((i << 1) | 1, mid + 1, r);
}
void push_up(int i)
{
if(Tree[i<<1].a > Tree[(i << 1)| 1].a){
Tree[i].a = Tree[i<<1].a ;
Tree[i].pa = Tree[i << 1].pa;
}else{
Tree[i].a = Tree[(i<<1) | 1].a ;
Tree[i].pa = Tree[(i << 1) | 1].pa;
}
if(Tree[i<<1].b < Tree[(i << 1)| 1].b){
Tree[i].b = Tree[i<<1].b ;
Tree[i].pb = Tree[i << 1].pb;
}else{
Tree[i].b = Tree[(i<<1) | 1].b ;
Tree[i].pb = Tree[(i << 1) | 1].pb;
}
}
int querymax(int i, int l, int r)
{
if(Tree[i].l == l && Tree[i].r == r){
ans[Tree[i].a] = Tree[i].pa;
return Tree[i].a;
}
int mid = Tree[i].l + Tree[i].r >> 1;
if(r <= mid) return querymax(i<<1, l, r);
else if(l > mid) return querymax((i << 1)|1, l, r);
else return max(querymax(i << 1, l, mid), querymax((i << 1)|1, mid + 1, r));
}
int querymin(int i, int l, int r)
{
if(Tree[i].l == l && Tree[i].r == r){
ans[Tree[i].b] = Tree[i].pb;
return Tree[i].b;
}
int mid = Tree[i].l + Tree[i].r >> 1;
if(r <= mid) return querymin(i<<1, l, r);
else if(l > mid) return querymin((i << 1)|1, l, r);
else return min(querymin(i << 1, l, mid), querymin((i << 1)|1, mid + 1, r));
}
void update(int i, int k, int x)
{
if(Tree[i].l == k && Tree[i].r == k){
Tree[i].a = Tree[i].b = x;
return;
}
int mid = Tree[i].l + Tree[i].r >> 1;
if(k <= mid) update(i << 1, k, x);
else update((i << 1) | 1, k, x);
push_up(i);
}
int main (void)
{
int n, m;sa(n);sa(m);
build(1, 0, n - 1);
for(int i = 0; i < n; i++){
sa(a[i]);
update(1, i, a[i]);
}
int l, r, x;
int a, b;
for(int i = 0; i < m; i++){
sa(l);sa(r);sa(x);
a = querymax(1, l - 1, r - 1);
b = querymin(1, l - 1, r - 1);
if(a == b && a == x) puts("-1");
else if(a == x) printf("%d\n", ans[b] + 1);
else printf("%d\n", ans[a] + 1);
}
return 0;
}
DP 方法
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define sa(n) scanf("%d", &(n))
const int maxn = 6e5 + 5, maxm = 1e6 + 5, oo = 0x3f3f3f3f;
int dp[maxn];
int a[maxn];
int main (void)
{
int n, m;sa(n);sa(m);
memset(dp, -1, sizeof(dp));
for(int i = 1; i <= n; i++){
sa(a[i]);
if(a[i] == a[i - 1]) dp[i] = dp[i - 1];
else dp[i] = i - 1;
}
int l, r, t;
for(int i = 0; i < m; i++){
sa(l);sa(r);sa(t);
if(a[r] == t){
if(dp[r] < l) cout<<-1<<endl;
else cout<<dp[r]<<endl;
}else cout<<r<<endl;
}
return 0;
}
一堆石头,给定长宽高,每种石头均可以使用无数次,问这堆石头可以叠放的最高高度,要求下面的石头的长和宽分别严格大于上面石头的长和宽。
采用DAG最长路算法,由于长宽较大,不能直接用于表示状态,因此采用 dp[i][x] 表示以第 i 块石头为最高点,以其第 x 个边为高所能达到的最大高度,x代表长/宽/高,然后根据长宽高要求构造DAG,最后记忆化搜索求出最长路。
/************************************************************************* > Author: jiangyuzhu > Mail: [email protected] ************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 30 + 5, oo = 0x3f3f3f3f;
int dp[maxn][maxn];
int p[maxn][3];
int u1, u2, l1, l2;
int n;
bool G[maxn][3][maxn][3];
bool check(int i, int k, int j, int q)// i 是否可以放在 j 上
{
if(k == 1) u1 = 2, u2 = 0;
if(k == 0) u1 = 2, u2 = 1;
if(k == 2) u1 = 1, u2 = 0;
if(q == 1) l1 = 2, l2 = 0;
if(q == 2) l1 = 1, l2 = 0;
if(q == 0) l1 = 1, l2 = 2;
int a1 = max(p[i][u1], p[i][u2]);
int a2 = min(p[i][u1], p[i][u2]);
int b1 = max(p[j][l1], p[j][l2]);
int b2 = min(p[j][l1], p[j][l2]);
return a1 < b1 && a2 < b2;
}
int get(int i, int k)
{
if(dp[i][k]) return dp[i][k];
int &ans = dp[i][k];
for(int a = 1; a <= n; a++){
for(int b = 0; b < 3; b++){
if(G[i][k][a][b])
ans = max(ans, get(a, b));
}
}
ans += p[i][k];
return ans;
}
int main (void)
{
int kas = 1;
while(~scanf("%d", &n) && n){
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++){
sa(p[i][0]);sa(p[i][1]);sa(p[i][2]);
}
memset(G, false, sizeof(G));
for(int i = 1; i <= n; i++){
for(int k = 0; k < 3; k ++){
for(int j = 1; j <= n; j++){
for(int q = 0; q < 3; q++){
if(check(i, k, j, q)){
G[i][k][j][q] = true;
}
}
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++){
for(int j = 0; j < 3; j++){
ans = max(ans, get(i, j));
}
}
cout<<"Case "<<kas++<<": maximum height = "<<ans<<endl;
}
return 0;
}