2019 ICPC Asia Nanjing Regional


2019 ICPC Asia Nanjing Regional

A - Hard Problem

計蒜客 - 42395

若 n = 10,可以先取:6,7,8,9,10。然后隨便從1,2,3,4,5里面選一個都肯定符合題意

若 n = 9,可以先取:5,6,7,8,9,然后隨便從1,2,3,4里面選一個都肯定符合題意。

所以答案就是后半部分的數量+1

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;

int T, n;

int main() {
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        if(n & 1)printf("%d\n",n/2+2);
        else printf("%d\n", n/2+1);
    }
    return 0;
}

B.Chessboard

首先當\(n=1\)或者\(m=1\)的時候只有兩種方法,從上往下刷或者從下往上刷,特判輸出2。

考慮一般情況。

怎么樣的粉刷策略是非法的?

當我刷着刷着凸出來一塊就是非法的。

因為我只要凸出來一塊,一定能導致某一對塗了色的方塊之間的距離大於曼哈頓距離。

所以合法的情況就是,只能要么一次增加一行,或者增加一列。

那么從最開始的\(1\times 1\)的矩陣變成\(n\times m\)的矩陣需要\(n+m-2\)次操作,其中有\(n-1\)次是行擴張,所以答案為:\(C_{n+m-2}^{n-1}\)

因為我們每次都是增加一行,或者增加一列,所以終點一定在四個角上,所以最后還要對答案乘以4。

這里我用\(lucas\)寫的,其實也可以\(nlogn\)預處理后\(O(1)\)出結果,不過都差不多,不計較。

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
const int maxn = 1e6+10;
typedef long long ll;
int n, m;
ll fact[maxn];
ll qmi(ll a, ll b, ll p)
{
	ll res = 1; res %= p;
	while(b)
	{
		if(b & 1) res = res*a%p;
		a = a*a%p;
		b >>= 1;
	}
	return res;
}

ll C(ll n, ll m, ll p)
{
	if(m > n) return 0;
	return ((fact[n]*qmi(fact[m],mod-2,mod))%mod*qmi(fact[n-m],mod-2,mod)%mod);
}

ll lucas(ll a, ll b, ll p)
{
	if(b == 0) return 1;
	return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p;
}

void solve()
{
    scanf("%d%d", &n, &m);
    if(n == 1 || m == 1)
    {
        puts("2");
        return;
    }

    ll ans = lucas(n+m-2, n-1, mod);
    ans = (ans*4)%mod;
    printf("%lld\n", ans);
}

int main()
{
    fact[0] = 1;
    for(int i = 1; i <= (int)1e6; i++)
        fact[i] = (fact[i-1]*i)%mod;
    int T; scanf("%d", &T);
    while(T--) solve();
    return 0;
}

C - Digital Path

計蒜客 - 42397

相鄰的兩個點如果權值相差為1,則從小的向大的連邊。然后最終連成的是一個DAG,在DAG上面按照拓撲序DP即可。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 1000000 + 5;
const int M = 4000005;
const int mod = 1e9 + 7;
int head[N], ver[M], nxt[M], edge[M], deg[N], tot;
int ends[N];
void add(int x, int y){
    deg[y]++;
    ver[++tot] = y, nxt[tot] = head[x], head[x] = tot;
}
int n, m, a[1010][1010];
ll d[N][5];// d[x][4]表示以x為終點,長度大於等於4的路徑個數,d[x][3]表示長度等於3的路徑個數,其他類似
inline int id(int x, int y){
    return (x-1)*m+y;
}
int main() {
    scanf("%d%d",&n,&m);
    memset(a, 0xcf, sizeof a); //注意這里的初始化
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int x = id(i, j);
            if(a[i+1][j] == a[i][j] + 1) add(x, id(i+1,j));
            if(a[i][j+1] == a[i][j] + 1) add(x, id(i,j+1));
            if(a[i-1][j] == a[i][j] + 1) add(x, id(i-1,j));
            if(a[i][j-1] == a[i][j] + 1) add(x, id(i,j-1));
        }
    }
    queue<int> q;
    for(int i=1;i<=n*m;i++)if(deg[i] == 0)q.push(i),d[i][1] = 1;
    ll res = 0;
    while(q.size()){
        int x = q.front();q.pop();
        bool flag = true;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            d[y][2] = (d[y][2] + d[x][1]) % mod;
            d[y][3] = (d[y][3] + d[x][2]) % mod;
            d[y][4] = (d[y][4] + d[x][3] + d[x][4])%mod;
            if(--deg[y] == 0)q.push(y);
            flag = false;
        }
        if(flag) res = (res + d[x][4])%mod;//如果出度為0則記錄到答案中
    }
    printf("%lld\n",res);
    return 0;
}

F - Paper Grading

計蒜客 - 42400

trie樹跑dfs建樹狀數組套權值線段樹

首先將字符串都加到Trie中,然后標記 \(f[i]\) 為每個字符串的最后一個字符所指定的Trie結點,考慮每次詢問,在Trie中向下走 k 次,對於可以找到合法結點的情況,答案就是\(f[l..r]\) 中,屬於該合法結點子樹的個數。

到這里,先不考慮修改操作,可以把Trie按dfs序建立主席樹,\(dfn[i]\) 表示第 i 號結點的dfs序,然后對每個\(f[i]\), 在第\(dfn[f[i]]\) 棵權值線段樹的 \(i\) 位置的權值+1,然后每次查詢子樹區間中有多少個數字在\([l,r]\)的范圍內。

加上修改操作,可以利用樹狀數組維護權值線段樹,交換兩個字符串的位置,也就是交換了$ f[i]$,直接在權值線段樹中修改即可

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 200000 + 5;
int n, m, L[30][30], R[30][30], cl, cr;
int f[N];
char s[N];
inline int lowbit(int x){return x & -x;}
struct SEG{
    struct node{
        int ls,rs;
        int sum;
    }t[N*200];
    int rt[N], tot, maxn, res;
    void update(int &rt, int l, int r, int pos, int val){
        if(!rt) rt = ++tot;
        t[rt].sum += val;
        if(l == r)return;
        int mid = l + r >> 1;
        if(pos <= mid) update(t[rt].ls, l, mid, pos, val);
        else update(t[rt].rs, mid+1, r, pos, val);
    }
    void update(int x, int pos, int v){
        for(;x<=maxn;x+=lowbit(x))update(rt[x],1,n,pos,v);
    }
    void query(int dep, int l, int r, int ql, int qr){
        if(ql > qr) return;
        if(l >= ql && r <= qr){
            for(int i=1;i<=cl;i++) res -= t[L[dep][i]].sum;
            for(int i=1;i<=cr;i++) res += t[R[dep][i]].sum;
            return;
        }
        int mid = l+r>>1;
        if(ql <= mid){
            for(int i=1;i<=cl;i++)L[dep+1][i] = t[L[dep][i]].ls;
            for(int j=1;j<=cr;j++)R[dep+1][j] = t[R[dep][j]].ls;
            query(dep+1,l,mid,ql,qr);
        }
        if(qr > mid){
            for(int i=1;i<=cl;i++)L[dep+1][i] = t[L[dep][i]].rs;
            for(int j=1;j<=cr;j++)R[dep+1][j] = t[R[dep][j]].rs;
            query(dep+1,mid+1,r,ql,qr);
        }
    }
}seg;
struct Trie{
    int ch[N][26], tot;
    int dfn[N], cid[N], out[N], cnt;
    int ins(char *s){
        int p = 1, len = strlen(s);
        for(int i=0;i<len;i++){
            int c = s[i] - 'a';
            if(!ch[p][c]) ch[p][c] = ++tot;
            p = ch[p][c];
        }
        return p;
    }
    void dfs(int x){
        dfn[x] = ++cnt, cid[cnt] = x;
        for(int i=0;i<26;i++){
            if(ch[x][i]) dfs(ch[x][i]);
        }
        out[x] = cnt;
    }
    int find(char *s, int k){
        int p = 1;
        for(int i=0;i<k;i++){
            int c = s[i] - 'a';
            if(!ch[p][c]) return -1;
            p = ch[p][c];
        }
        return p;
    }
}trie;

int main() {
    scanf("%d%d",&n,&m);
    trie.tot = 1;
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        f[i] = trie.ins(s);
    }
    trie.dfs(1);
    seg.maxn = trie.cnt;//maxn為權值線段樹的個數
    for(int i=1;i<=n;i++){
        f[i] = trie.dfn[f[i]];//這里將f進一步映射到了Trie結點的dfs序上
        seg.update(f[i], i, 1);
    }
    while(m--){
        int op, l, r, k;
        scanf("%d",&op);
        if(op == 1){
            scanf("%d%d",&l,&r);
            seg.update(f[l], l, -1);
            seg.update(f[r], r, -1);
            seg.update(f[r], l, 1);
            seg.update(f[l], r, 1);
            swap(f[l], f[r]);
        }else {
            scanf("%s%d%d%d",s, &k, &l, &r);
            int pos = trie.find(s, k);
            if(pos == -1){
                printf("0\n");continue;
            }
            int x = trie.dfn[pos]-1, y = trie.out[pos];
            cl = cr = 0;
            for(int j=x;j;j-=lowbit(j))L[0][++cl] = seg.rt[j];
            for(int j=y;j;j-=lowbit(j))R[0][++cr] = seg.rt[j];
            seg.res = 0;
            seg.query(0, 1, n, l, r);
            printf("%d\n",seg.res);
        }
    }
    return 0;
}

H - Prince and Princess

計蒜客 - 42402

a = 1, b = 0, c = 0, 則 res = 0

a > 1, b = 0, c = 0 則 res = 1

a > b, b > 0, c = 0, 則最壞情況下需要問 2*b+1個人 “公主在那個房間”,一定會得到一個 b+1 次的答案

a < b, b > 0, c = 0, 則 每個 b 都可以與a回答一樣的答案(因為他們的騙子),所以無法區分,也就無法得到正確結果

a > b + c, 則類似第三種的處理方式,詢問 2*(b+c) +1 次 “公主在那個房間”,出現次數最多的就是答案

a < b + c, 則無解

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <map>
using namespace std;
typedef long long ll;

int main() {
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);
	if(c == 0) {
		if(b == 0) {
			puts("YES");
			if(a == 1) puts("0");
            else puts("1");
		} else if(a > b) {
			puts("YES");
			printf("%d\n", b + b + 1);
		} else {
			puts("NO");
		}
	} else {
		if(a > b + c) {
			puts("YES");
			printf("%d\n", b + c + b + c + 1);
		} else {
			puts("NO");
		}
	}
	return 0;
}

J - Spy

計蒜客 - 42404

首先要轉換為二分圖帶權最大匹配問題,然后用KM算法去解決。

dfs版本的KM,每遞歸一次只會在相等子圖中增加一條邊,而每次遞歸都是極限 \(O(M)\) 的復雜度,所以當 \(M=N^2\) 時,復雜度將達到\(O(N^4)\)

即使加了\(slack\) 數據記錄最小值進行優化,那也無法避免這樣的情況。

BFS版本的KM,每次BFS復雜度是 \(O(M)\) 的,但每次BFS會擴展\(O(N)\) 條邊,所以總復雜度是\(O(N^3)\)

參考:https://blog.csdn.net/c20182030/article/details/73330556

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 605;

ll n, a[N],b[N],c[N],p[N];
ll w[N][N];
ll lx[N] , ly[N];
ll match[N];
ll slack[N];
bool vy[N];
ll pre[N];
void bfs( ll k ){
    ll x , y = 0 , yy = 0 , delta;
    memset( pre , 0 , sizeof(pre) );
    for( ll i = 1 ; i <= n ; i++ ) slack[i] = inf;
    match[y] = k;
    while(1){
        x = match[y]; delta = inf; vy[y] = true;
        for( ll i = 1 ; i <= n ;i++ ){
            if( !vy[i] ){
                if( slack[i] > lx[x] + ly[i] - w[x][i] ){
                    slack[i] = lx[x] + ly[i] - w[x][i];
                    pre[i] = y;
                }
                if( slack[i] < delta ) delta = slack[i] , yy = i ;
            }
        }
        for( ll i = 0 ; i <= n ; i++ ){
            if( vy[i] ) lx[match[i]] -= delta , ly[i] += delta;
            else slack[i] -= delta;
        }
        y = yy ;
        if( match[y] == -1 ) break;
    }
    while( y ) match[y] = match[pre[y]] , y = pre[y];
}
 
ll KM(){
    memset( lx , 0 ,sizeof(lx) );
    memset( ly , 0 ,sizeof(ly) );
    memset( match , -1, sizeof(match) );
    for( ll i = 1 ; i <= n ; i++ ){
        memset( vy , false , sizeof(vy) );
        bfs(i);
    }
    ll res = 0 ;
    for( ll i = 1 ; i <= n ; i++ ){
        if( match[i] != -1 ){
            res += w[match[i]][i] ;
        }
    }
    return res;
}
 
int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&p[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&b[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&c[i]);
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=n;j++){
            ll s=0;
            for(ll k=1;k<=n;k++){
                if(b[i]+c[j]>a[k]) s+=p[k];
            }
            w[i][j]=s;
        }
    }
    printf("%lld\n",KM());
    return 0;
}

K.Triangle:

這道題看起來比寫起來難。

首先考慮在不在點在不在線上,這屬於板子活,原理我也不咋懂,找了個板子抄了一下。

然后考慮幾個特殊情況,\(p\)點和三角形三個頂點\(A,B,C\)重合,那么一定是對邊的中點。

想到這里我們也可以發現一個很好的性質。

假設我的點落在\(AB\)上,如果點靠\(A\)更近一點,那么另一個點一定落在\(BC\)上;如果靠\(B\)近一點,那么另一個點一定落在\(AC\)上;如果落在\(AB\)中點,那么另一個點是三角形的頂點。

所以把點調整一下,讓點落在\(AB\)靠近\(A\)的地方,然后二分另一個點距離\(C\)的長度,最后根據長度的比例計算一下點的坐標就可以了。

因為題目要求\(1e-6\),所以我設置\(eps=1e-8\),開\(double\),不知道有沒有別的精度問題。

需要注意:提前處理出需要用的邊的長度,減少庫函數的調用。

#include<bits/stdc++.h>
using namespace std;
typedef double ld;
ld eps = 1e-8;

struct Point{
    ld x, y;
}a, b, c, p, q;
ld area;
ld ab, ac, bc, ap, bp;

bool onSegment(Point Pi, Point Pj, Point Q)
{
    if((Q.x - Pi.x) * (Pj.y - Pi.y) == (Pj.x - Pi.x) * (Q.y - Pi.y)  //叉乘
       //保證Q點坐標在pi,pj之間
       && min(Pi.x , Pj.x) <= Q.x && Q.x <= max(Pi.x , Pj.x)
       && min(Pi.y , Pj.y) <= Q.y && Q.y <= max(Pi.y , Pj.y))
        return true;
    else
        return false;
}

ld dist(Point a, Point b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

bool check(ld mid, ld sinb)
{
    ld are = sinb*(bp)*(bc-mid)/2;
    if(are*2.0 >= area) return 0;
    return 1;
}

void solve()
{
    scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &a.x, &a.y, &b.x, &b.y, &c.x, &c.y, &p.x, &p.y);
    //cin>>a.x>>a.y>>b.x>>b.y>>c.x>>c.y>>p.x>>p.y;
    if(!onSegment(a, b, p)&&!onSegment(b, c, p)&&!onSegment(a, c, p))
    {
        puts("-1");
        return;
    }

    //先看看會不會在某個點上
    if(p.x == a.x && p.y == a.y)
    {
        //輸出bc中點
        q.x = (b.x+c.x)/2.0;
        q.y = (b.y+c.y)/2.0;
        //cout << setprecision(10) << q.x << " " << q.y << endl;
        printf("%.10f %.10f\n", q.x, q.y);
        return;
    }
    else if(p.x == b.x && p.y == b.y)
    {
        //ac中點
        q.x = (a.x+c.x)/2.0;
        q.y = (a.y+c.y)/2.0;
        //cout << setprecision(10) << q.x << " " << q.y << endl;
        printf("%.10f %.10f\n", q.x, q.y);
        return;
    }
    else if(p.x == c.x && p.y == c.y)
    {
        //ab中點
        q.x = (a.x+b.x)/2.0;
        q.y = (a.y+b.y)/2.0;
       // cout << setprecision(10) << q.x << " " << q.y << endl;
        printf("%.10f %.10f\n", q.x, q.y);
        return;
    }


    //把點弄到ab上
    if(onSegment(a, b, p)) {}
    else if(onSegment(b, c, p)) swap(a, c);
    else if(onSegment(a, c, p)) swap(b, c);

    //距離b更近一點
    if(dist(a, p) > dist(b, p)) swap(a, b);

    //開始搞
    ab = dist(a, b);
    ac = dist(a, c);
    bc = dist(b, c);
    ap = dist(a, p);
    bp = dist(b, p);

    ld p = (ab+bc+ac)/(ld)2.0;
    area = sqrt(p*(p-ab)*(p-bc)*(p-ac));
    ld sina = area*2/ab/bc;
    //area = ab*bc*sinb/2
    //sina = area*2/ab/bc

    ld l = 0.0, r = bc;
    while(l+eps < r)
    {
        ld mid = (l+r)/(ld)2.0;
        if(check(mid, sina)) r = mid;
        else l = mid;
    }

    ld len = l;
    //輸出點
    q.x = (b.x-c.x)*len/bc+c.x;
    q.y = (b.y-c.y)*len/bc+c.y;
    //cout << setprecision(10) << q.x << " " << q.y << endl;
    printf("%.10f %.10f\n", q.x, q.y);
    return;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--) solve();
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM