【补题报告】ZJNU | 2021-03-11 个人排位赛2 | B E G J

补充

B \mathfrak{B} B Stampede | 离散化+线段树

  • 【题意】
    N N N 头牛,一开始每头牛左段坐标为 ( x i , y i ) (x_i,y_i) (xi,yi),右段坐标为 ( x i + 1 , y i ) (x_i+1,y_i) (xi+1,yi),拟成一条线段。
    开始后,每头牛一直向右跑,花 r i r_i ri时间跑完一格
    在原点,有一根向 y y y 轴正方向的射线。问你最终有多少头牛能被射线照射到。
  • 【范围】
    1 ≤ N ≤ 5 × 1 0 4 1\le N\le 5\times10^4 1N5×104
    x i ∈ [ − 1000 , − 1 ] x_i\in[-1000,-1] xi[1000,1]
    y i ∈ [ 1 , 1 0 6 ] y_i\in[1,10^6] yi[1,106]
    r i ∈ [ 1 , 1 0 6 ] r_i\in[1,10^6] ri[1,106]
    保证 y i y_i yi 两两不相同。
  • 【思路】
    每头牛触碰 y y y 轴的时间计算得到 [ s i , e i ] [s_i,e_i] [si,ei]
    我们首先按 y i y_i yi 升序。如果 [ s i , e i ] [s_i,e_i] [si,ei] 当中所有时间都被覆盖了,那么这头牛不能被射线射到。反之,可以被射线射到,然后覆盖了这一段的时间。
    由于时间很大,我们可以离散化记录,然后使用线段树查询区间最小值区间覆盖即可。
  • 【细节】
    然后自己敲的时候发现了一个问题,如果时间段 [ 1 , 2 ] [1,2] [1,2] 和 时间段 [ 3 , 4 ] [3,4] [3,4] 都覆盖了,新来了一头牛,它的时间段为 [ 2 , 3 ] [2,3] [2,3],显然它会被激光照射到。但是我们使用线段树覆盖之后,每个顶点都打上了访问过的标记。
    我的解决方法是:我们区间覆盖记录的是左闭右开的区间。因为没有区间会是 [ x , x ] [x,x] [x,x] 的形式,左闭右开对答案不影响。
  • 【代码】
    时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
struct node{
    ll x,y,r;
    bool operator <(const node &ND)const{
        return y < ND.y;
    }
    ll s,e;
}aa[MAX];
set<ll>S;
unordered_map<ll,int>M;
int cnt;


const int MAX_LEN = MAX;

int seg_tree[MAX_LEN << 2];
int Lazy[MAX_LEN << 2];
//从下往上更新 节点
void push_up (int root) {
	seg_tree[root] = min(seg_tree[root << 1], seg_tree[root << 1 | 1]);  	//最小值改min
}
//从上向下更新,左右孩子
void push_down (int root, int L, int R) {
	if(Lazy[root]){
		Lazy[root << 1] += Lazy [root];
		Lazy[root << 1 | 1] += Lazy[root];
		int mid = (L + R) >> 1;
		seg_tree[root << 1] +=  Lazy[root] * (mid - L + 1);
		seg_tree[root << 1 | 1] += Lazy[root] * (R - mid);
		Lazy[root] = 0;
	}
}
//建树
void build (int root, int L, int R) {
	Lazy[root] = 0;
	if(L == R) {
		seg_tree[root] = 0;
		return ;
	}
	int mid = (L + R) >> 1;
	build(root << 1, L, mid);
	build(root << 1 | 1, mid + 1, R);
	push_up(root);
}

//区间查询
int query (int root, int L, int R, int LL, int RR) {
	if (LL <= L && R <= RR) return seg_tree[root];
	push_down(root, L, R); 	//每次访问都去检查Lazy 标记
	int Ans = INF;
	int mid = (L + R) >> 1;
	if(LL <= mid) Ans = min(Ans, query(root << 1, L, mid, LL, RR));	//最小值改min
	if(RR > mid) Ans = min(Ans, query(root << 1 | 1, mid + 1, R, LL, RR)); //最小值改min
	return Ans;
}
//区间修改 +-某值
void update_Interval(int root, int L, int R, int LL, int RR, int val){
	 if (LL <= L && R <= RR) {
	 	Lazy[root] = val;
	 	seg_tree[root] = val * (R - L + 1);
		return ;
	 }
	 push_down(root, L, R);
	 int mid = (L + R) >> 1;
	 if (LL <= mid) update_Interval(root << 1, L, mid, LL, RR, val);
	 if (RR > mid) update_Interval(root << 1 | 1, mid + 1, R, LL , RR, val);
	 push_up(root);
}

int main(){
    int n;scanf("%d",&n);
    for(int i = 1;i <= n;++i){
        scanf("%lld%lld%lld",&aa[i].x,&aa[i].y,&aa[i].r);
    }
    sort(aa+1,aa+1+n);

    for(int i = 1;i <= n;++i){
        aa[i].s = (-aa[i].x - 1) * aa[i].r;
        aa[i].e = aa[i].s + aa[i].r;
        S.insert(aa[i].s);
        S.insert(aa[i].e);
    }
    for(auto it : S){
        M[it] = ++cnt;
    }
    build(1,1,cnt);
    int ans = 0;
    for(int i = 1;i <= n;++i){
        int mn = query(1,1,cnt,M[aa[i].s],M[aa[i].e]-1);
        if(mn == 0){
            ans++;
            update_Interval(1,1,cnt,M[aa[i].s],M[aa[i].e]-1,1);
        }

    }
    printf("%d",ans);
    return 0;
}

E \mathfrak{E} E Cow Routing III | 优化建图

  • 【题意】
    N N N 个飞机航班,每个飞机航班有一个花费 c o s t i cost_i costi ,还有一条单向的路线 a 1 ⋯ a k a_1\cdots a_k a1ak
    如果你乘了某个航班,你就只需要花费 c o s t i cost_i costi,不管你之后这个航班坐了几站 。但是你可以在任意位置上机,也可以在任意位置下机。
    问你从 A A A B B B ,最小花费是多少。在最小花费下,最少站点经过数是多少。
  • 【范围】
    1 ≤ N ≤ 1000 1\le N\le 1000 1N1000
    1 ≤ c o s t i ≤ 1 0 9 1\le cost_i\le 10^9 1costi109
    1 ≤ k ≤ 100 1\le k\le 100 1k100
    城市编号 1 ≤ x ≤ 1000 1\le x \le 1000 1x1000
  • 【思路】
    如果暴力建图,那么点数为 1 0 3 10^3 103,边数为 N × k 2 N\times k^2 N×k2,暴力去跑可能可以卡过,但是不优秀。
    考虑优化建图
    就像机场一样,我们每一个点可以跑到该站点的车上,站点经过费用 0 0 0金钱花费 c o s t i cost_i costi
    在这个站点的车跑到下个站点的车的站点经过费用 1 1 1金钱花费 0 0 0
    在这个站点的车下车到该站点的站点经过费用 0 0 0金钱花费 0 0 0
    在这里插入图片描述
    这样建图之后,点数为 1 0 3 + N k 10^3+Nk 103+Nk,边数为 3 N k 3Nk 3Nk,再跑最短路就会优秀地多了。
  • 【代码】
    时间复杂度: O ( E log ⁡ E ) O(E\log E) O(ElogE),其中 E = 3 N k E=3Nk E=3Nk
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
int ex = 1001;
struct node{
    int to;
    ll v1;
    int v2;
    bool operator <(const node &ND)const{
        if(v1 != ND.v1)return v1 > ND.v1;
        return v2 > ND.v2;
    }
};
vector<node>V[MAX];
int aa[MAX];
ll dp1[MAX];
int dp2[MAX];

void dijkstra(int S,int T){
    priority_queue<node>Q;
    dp1[S] = dp2[S] = 0;
    Q.push({S,0,0});
    while(!Q.empty()){
        int x = Q.top().to;
        ll v1 = Q.top().v1;
        int v2 = Q.top().v2;
        Q.pop();
        if(v1 > dp1[x])continue;
        if(v2 > dp2[x])continue;

        for(auto it : V[x]){
            int to = it.to;
            ll d1 = v1 + it.v1;
            int d2 = v2 + it.v2;
            if(d1 < dp1[to]){
                dp1[to] = d1;
                dp2[to] = d2;
                Q.push({to,d1,d2});
            }else if(d1 == dp1[to] && d2 < dp2[to]){
                dp2[to] = d2;
                Q.push({to,d1,d2});
            }
        }
    }
}

int main(){
    int A,B,n;
    scanf("%d%d%d",&A,&B,&n);
    for(int i = 1;i <= n;++i){
        ll cost;int k;scanf("%lld%d",&cost,&k);

        for(int j = 1;j <= k;++j){
            scanf("%d",&aa[j]);
            V[aa[j]].push_back({ex,cost,0});
            V[ex].push_back({aa[j],0,0});

            if(j > 1)V[ex - 1].push_back({ex,0LL,1});
            ex++;
        }
    }
    for(int i = 1;i <= ex;++i){
        dp1[i] = LINF;
        dp2[i] = INF;
    }
    dijkstra(A,B);
    if(dp1[B] == LINF){
        puts("-1 -1");
    }else{
        printf("%lld %d",dp1[B],dp2[B]);
    }
    return 0;
}

G \mathfrak{G} G Moovie Mooving | 状压 d p dp dp + 二分

  • 【题意】
    N N N 部电影,每部的长度为 D i D_i Di,每部有 C i C_i Ci 次放映的场次,每个放映的场次的开始时间都给定。
    问你,从时刻 0 0 0 到时刻 T T T,你能否安排看电影的某种次序,首先要求每部电影最多只看一次,其次要求 0 ∼ T 0\sim T 0T 时刻你都一直在看电影。
    对于某一个场次的电影, 只要在放映时间内,你可以选择中途开始看,或者中途就离开。
    问你,满足前面的要求下,你选择的电影场次最少是多少?
  • 【范围】
    1 ≤ N ≤ 20 1\le N\le 20 1N20
    1 ≤ D i ≤ T ≤ 1 0 9 1\le D_i\le T\le 10^9 1DiT109
    1 ≤ C i ≤ 1 0 3 1\le C_i\le 10^3 1Ci103
  • 【思路】
    N = 20 N=20 N=20,基本上就是状压了。我们设 d p [ S ] dp[S] dp[S] 表示看了状态(集合) S S S 的电影后,能一直看电影的最长长度
    考虑到状态转移,我们假设 2 j & i ≠ 0 2^j\&i\ne 0 2j&i=0,也就是说可以从状态 i − 2 j i-2^j i2j 推到状态 i i i
    我们已经获得了 d p [ i − 2 j ] dp[i-2^j] dp[i2j] 的值了,接下来需要看第 j j j 部电影。我们从什么时候开始看是最优的呢?就是从所有能看的场次里,选择看完出来最晚的那一场。由于电影长度是固定的,所以我们使用 u p p e r _ b o u n d upper\_bound upper_bound 函数快速算出那场即可。
  • 【代码】
    时间复杂度: O ( 2 n × log ⁡ 1 0 3 ) O(2^n\times \log 10^3) O(2n×log103)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
vector<int>V[25];
int bb[25];
int dp[MAX];

int solve(int x,int d){
	/// 算出第一个开始段超过d的下标,然后往前一场就是开始段不超过d的下标
    int pos = upper_bound(V[x].begin(),V[x].end(),d) - V[x].begin();
    pos--;
    if(pos < 0)return -INF;					/// 非法场次,设为-INF
    if(V[x][pos] + bb[x] < d)return -INF;
    return V[x][pos] + bb[x];
}

int main(){
    int n,T;cin >> n >> T;
    for(int i = 0;i < n;++i){
        int shu;
        cin >> bb[i] >> shu;
        while(shu--){
            int t;cin >> t;
            V[i].push_back(t);
        }
    }
    int ed = qpow(2,n);
    int shu = INF;
    for(int i = 1;i < ed;++i){
        int tshu = 0;
        for(int j = 0;j < n;++j){
            if((1<<j) & i){
                tshu++;
                dp[i] = max(dp[i],solve(j,dp[i-(1<<j)]));
            }
        }
        if(dp[i] >= T)shu = min(shu,tshu);
    }
    printf("%d",shu == INF ? -1 : shu);
    return 0;
}

J \mathfrak{J} J Palindromic Paths II | d p dp dp + 滚动数组优化

  • 【题意】
    N × N N\times N N×N 的字母矩阵,求从 ( 1 , 1 ) (1,1) (1,1),每步可以向下或者向右一个单位,走到终点 ( N , N ) (N,N) (N,N)字符串为回文串路径个数
  • 【范围】
    1 ≤ N ≤ 500 1\le N\le 500 1N500
  • 【思路】
    我们需要起点 ( 1 , 1 ) (1,1) (1,1),每步向右/下走到对角线,然后终点 ( n , n ) (n,n) (n,n),每步向左/上走到对角线。
    我们设 d p [ i ] [ j ] [ k ] [ p ] dp[i][j][k][p] dp[i][j][k][p] 表示起点走到 ( i , j ) (i,j) (i,j),终点走到 ( k , p ) (k,p) (k,p) 时候的合法路径数量
    由于是四维,时间复杂度至少为 O ( N 4 ) O(N^4) O(N4),显然不行。
    考虑到,起点走 x x x 步,终点也是走 x x x 步。所以我们有
    ( i − 1 ) + ( j − 1 ) = ( n − k ) + ( n − p ) (i-1)+(j-1)=(n-k)+(n-p) (i1)+(j1)=(nk)+(np)
    所以我们得到了: p = 2 n + 2 − k − i − j p=2n+2-k-i-j p=2n+2kij,于是我们的 d p dp dp 可以省略一维,变成 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]
    考虑到状态转移。 ( i , j ) (i,j) (i,j) 下一步走到 ( i i , j j ) (ii,jj) (ii,jj) 有两种方案, ( k , p ) (k,p) (k,p) 下一步走到 ( k k , p p ) (kk,pp) (kk,pp) 也有两种方案。
    我们枚举这四种方案,状态转移方程就变成了
    d p [ i i ] [ j j ] [ k k ] = + d p [ i ] [ j ] [ k ] , 满 足 a a [ i i ] [ j j ] = a a [ k k ] [ p p ] dp[ii][jj][kk]\overset{+}{=}dp[i][j][k],满足aa[ii][jj]=aa[kk][pp] dp[ii][jj][kk]=+dp[i][j][k]aa[ii][jj]=aa[kk][pp]
    最终答案即为 ∑ i d p [ i i ] [ i i ] [ i i ] \sum_idp[ii][ii][ii] idp[ii][ii][ii]
    但是这样会 M L E \color{red}MLE MLE,我们考虑还要优化。按照滚动数组进行优化。
    因为每次算完 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k],算完所有的 i i i,接下来的 d p [ i + 1 ] [ j ] [ k ] dp[i+1][j][k] dp[i+1][j][k] 以至于后面的 i i i 都不需要这些没有用的状态了。
    于是我们可以第一维滚动一下,让空间复杂度变为 O ( N 2 ) O(N^2) O(N2)
    我这里有两种优化方案。就是时间和空间的权衡方案了。
  • 【优化方案一】
    T i m e ( M s ) : 1906 / 2000 Time(Ms):1906/2000 Time(Ms):1906/2000
    M e m o ( K b ) : 6710 Memo(Kb):6710 Memo(Kb):6710
    时间复杂度: O ( 5 N 3 ) O(5N^3) O(5N3)
    空间复杂度: O ( 4 N 2 ) O(4N^2) O(4N2)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
int n;
char aa[MAX][MAX];
ll   dp[2][MAX][MAX];
ll   dp2[MAX][MAX];
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;++i)scanf("%s",aa[i] + 1);
    
    if(aa[1][1] != aa[n][n]){
        puts("0");
        return 0;
    }
    ll res = 0;
    dp[1][1][n] = 1;
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= n;++j){

            for(int k = 1;k <= n;++k){
                dp[0][j][k] = (dp2[j][k] + dp[1][j][k]) % MOD;
                dp[1][j][k] = 0;
                dp2[j][k]   = 0;
            }

            if(i + j >= n + 1)break;

            for(int k = n;k >= 1;--k){
                int l = 2 * n + 2 - i - j - k;
                if(l < 1 || l > n)continue;
                if(aa[i][j] != aa[k][l])continue;
                if(dp[0][j][k] == 0)continue;
                for(int d1 = 0;d1 < 2;++d1)
                for(int d2 = 0;d2 < 2;++d2){
                    int ii,jj,kk,pp;
                    if(d1 == 0){
                        ii = i + 1;
                        jj = j;
                    }else{
                        ii = i;
                        jj = j + 1;
                    }

                    if(d2 == 0){
                        kk = k - 1;
                        pp = l;
                    }else{
                        kk = k;
                        pp = l - 1;
                    }
                    if(aa[ii][jj] == aa[kk][pp]){
                        if(ii == kk){
                            dp[1][jj][kk] = (dp[1][jj][kk] + dp[0][j][k]) % MOD;
                        }else{
                            dp2[jj][kk] = (dp2[jj][kk] + dp[0][j][k]) % MOD;
                        }
                        if(ii == kk && jj == pp){
                            res = (res + dp[0][j][k]) % MOD;
                        }
                    }
                }

            }
        }

    }
    printf("%lld",res);
    return 0;
}
  • 【优化方案二】
    T i m e ( M s ) : 1953 / 2000 Time(Ms):1953/2000 Time(Ms):1953/2000
    M e m o ( K b ) : 2398 Memo(Kb):2398 Memo(Kb):2398
    时间复杂度: O ( 6 N 3 ) O(6N^3) O(6N3)
    空间复杂度: O ( 3 N 2 ) O(3N^2) O(3N2)
    这里开 l l ll ll 就过不了了,得开 i n t int int
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
int n;
char aa[MAX][MAX];
int   dp[2][MAX][MAX];
int  dp2[MAX];
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;++i)scanf("%s",aa[i] + 1);

    if(aa[1][1] != aa[n][n]){
        puts("0");
        return 0;
    }

    int res = 0;
    dp[1][1][n] = 1;
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= n;++j)
        for(int k = 1;k <= n;++k){
            dp[0][j][k] = dp[1][j][k];
            dp[1][j][k] = 0;
        }
        for(int j = 1;j <= n;++j){
            for(int k = n;k >= 1;--k){
                dp[0][j][k] = ((ll)dp[0][j][k] + dp2[k]) % MOD;
                dp2[k] = 0;
            }
            if(i + j >= n + 1)break;
            for(int k = n;k >= 1;--k){
                int l = 2 * n + 2 - i - j - k;
                if(l < 1)break;

                if(aa[i][j] != aa[k][l])continue;
                if(dp[0][j][k] == 0)continue;

                for(int d1 = 0;d1 < 2;++d1)
                for(int d2 = 0;d2 < 2;++d2){
                    int ii,jj,kk,pp;
                    if(d1 == 0){
                        ii = i + 1;
                        jj = j;
                    }else{
                        ii = i;
                        jj = j + 1;
                    }

                    if(d2 == 0){
                        kk = k - 1;
                        pp = l;
                    }else{
                        kk = k;
                        pp = l - 1;
                    }
                    if(aa[ii][jj] == aa[kk][pp]){
                        if(i == ii){
                            dp2[kk] = ((ll)dp2[kk] + dp[0][j][k]) % MOD;
                        }else{
                            dp[1][jj][kk] = ((ll)dp[1][jj][kk] + dp[0][j][k]) % MOD;
                        }
                        if(ii == kk && jj == pp){
                            res = ((ll)res + dp[0][j][k]) % MOD;
                        }
                    }
                }

            }
        }
    }
    printf("%d",res);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值