1. 程式人生 > 其它 >2022年TZC集訓隊省賽選拔賽1題解

2022年TZC集訓隊省賽選拔賽1題解

A(*)列舉

題意是找到

  • 相鄰差值最大的一對數
    • 如果差值一樣,則選擇高度最高的那個
      • 如果高度也一樣,則選擇編號最大的數
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int w[N],n;
int res=2;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    for(int i=2;i<=n;i++){
        if(abs(w[i]-w[i-1])>abs(w[res]-w[res-1])){
            res=i;
        }
        else if(abs(w[i]-w[i-1])==abs(w[res]-w[res-1])){
            if(max(w[i],w[i-1])>=max(w[res],w[res-1]))res=i;
        }
    }
    printf("%d %d\n",res-1,res);
    return 0;
}

C(*)結構體排序

對每個字串進行平均值排序,為了防止精度問題,將 / 換成 *

#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
map<string,int>mp;
int idx=0;
struct aa{
    string s;
    int sum,cnt;
    bool operator <(const aa &c)const{
        return (ll)sum*c.cnt<(ll)c.sum*cnt;
    }
}w[510];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        string s;
        cin>>s;
        if(!mp.count(s)){
            ++idx;mp[s]=idx;
            w[idx]={s,i,1};
        }
        else{
            int c=mp[s];
            w[c].sum+=i;
            w[c].cnt++;
        }
    }
    sort(w+1,w+1+idx);
    for(int i=1;i<=idx;i++)cout<<w[i].s<<endl;
    return 0;
}

H(**)樹的前序遍歷和後序遍歷

將每個遍歷後的編號存入陣列中
對於某個編號id而言,分別對應某個權值

  • 當 c[idx]為0時 -> x - y 不變
  • 當 c[idx]為1時
    • 前序遍歷位置的編號 > 後序遍歷位置的編號 -> x - y 增加
    • 前序遍歷位置的編號 < 後序遍歷位置的編號 -> x - y 減小
      所有列舉每個編號即可
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 1e5+5,mod=998244353;
vector<int>da,db;
ll d[N];
int s1[N],s2[N];
struct aa{
    int l,r;
}tr[N];
void dfs_front(int u){
    if(!u)return ;
    da.push_back(u);
    dfs_front(tr[u].l);
    dfs_front(tr[u].r);
}
void dfs_back(int u){
    if(!u)return ;
    dfs_back(tr[u].l);
    dfs_back(tr[u].r);
    db.push_back(u);
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        tr[i]={a,b};
    }
    dfs_front(1);
    dfs_back(1);
    d[0]=1;
    for(int i=1;i<=n;i++)d[i]=d[i-1]*2%mod;
    for(int i=0;i<da.size();i++)s1[da[i]]=i+1;
    for(int i=0;i<db.size();i++)s2[db[i]]=i+1;
    ll res=0;
    for(int i=1;i<=n;i++){
        if(s1[i]<s2[i]){
            ll c=(d[n-s1[i]]-d[n-s2[i]])%mod+mod;
            res=(res+c)%mod;
        }
    }
    printf("%lld\n",res);
    return 0;
}

B(**)暴力

列舉所有迴文串,判斷這個數是不是質數,存入陣列中
排序後通過二分得到比這個數小的迴文素數有哪些
即為 get( R ) - get( L-1 )

#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
vector<ll>vec{2,3,5,7};
int find(ll x){
    return lower_bound(vec.begin(),vec.end(),x)-vec.begin();
}
bool check(ll x){
    if(x<=1)return false;
    for(ll i=2;i<=x/i;i++){
        if(x%i==0)return false;
    }
    return true;
}
ll change(ll x){
    ll res=0;
    while(x){
        res=res*10+x%10;
        x/=10;
    }
    return res;
}
int get(ll x){
    int c=find(x);
    if(vec[c]==x)c++;
    return c;
}
int main(){
    ll l,r;
    ll f=10;
    //cout<<change(1411)<<endl;
    for(int i=1;i<1e4;i++){
        ll a=i,b= change(i);
        if(i==10||i==100||i==1000||i==10000)f*=10;
        if(check(a*f+b))vec.push_back(a*f+b);
        for(int j=0;j<10;j++){
            if(check((a*10+j)*f+b))vec.push_back((a*10+j)*f+b);
            //cout<<(a*10+j)*f+b<<endl;
        }
    }
    sort(vec.begin(),vec.end());
    scanf("%lld%lld",&l,&r);
    printf("%d\n",get(r)-get(l-1));
    return 0;
}

I(**)最短路問題

對商店,學校,家兩兩連邊,邊的權值為兩點的曼哈頓距離
套個最短路模板即可

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 110,INF=0x3f3f3f3f;
int d[N][N];
int f[N];
bool st[N];
int n,m,S,F;
int x[N],y[N];
int dijkstra(){
    memset(f,0x3f,sizeof(f));
    memset(st,0,sizeof(st));
    f[0]=0;
    queue<int>que;que.push(0);
    while(1){
        int u=-1;
        for(int i=0;i<=S+1;i++){
            if(!st[i]&&(u==-1||f[u]>f[i]))u=i;
        }
        if(u==-1)return f[S+1];
        st[u]=true;
        for(int i=0;i<=S+1;i++){
            f[i]=min(f[i],f[u]+d[u][i]);
        }
    }
}
int main(){
    scanf("%d%d%d%d",&n,&m,&F,&S);
    for(int i=1;i<=S;i++){
        scanf("%d%d",&x[i],&y[i]);
    }
    x[0]=y[0]=1;
    x[S+1]=n,y[S+1]=m;
    for(int i=0;i<=S+1;i++){
        for(int j=0;j<=S+1;j++){
            int c=abs(x[i]-x[j])+abs(y[i]-y[j]);
            if(c<=F)d[i][j]=c;
            else d[i][j]=INF;
        }
    }
    printf("%d\n",dijkstra());
    return 0;
}

J(***)線段樹求區間最大值

對於某個數字x而言,它可以從 y(abs{x-y}>=k)處轉移過來
因此就可以將陣列排序後離散化構建線段樹
x 從分別從左右兩個區間轉移過來(c = max{LEFT,RIGHT})

  • c + 1 > 原來的數,更新
  • c + 1 < 原來的數,不更新
    最後答案即為區間的最大值
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 3e5+5,INF=1e9;
int n,k;
int w[N];
vector<int>vec({-INF*2-5,INF*2+5});
struct aa{
    int l,r;
    int max;
}tr[N*4];
int find(int x){
    return lower_bound(vec.begin(), vec.end(),x)-vec.begin();
}
void pushup(int u){
    tr[u].max=max(tr[u<<1].max,tr[u<<1|1].max);
}
void build(int u,int l,int r){
    tr[u]={l,r,0};
    if(l==r){
        return ;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
}
void modify(int u,int x,int v){
    if(tr[u].l==x&&tr[u].r==x){
        tr[u].max=max(tr[u].max,v);
        return ;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(x<=mid)modify(u<<1,x,v);
    else modify(u<<1|1,x,v);
    pushup(u);
}
int query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r){
        return tr[u].max;
    }
    int mid=tr[u].l+tr[u].r>>1;
    int c=0;
    if(l<=mid)c= query(u<<1,l,r);
    if(r>mid)c=max(c, query(u<<1|1,l,r));
    return c;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
//        int a;
        scanf("%d",&w[i]);
        vec.push_back(w[i]);
    }
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    build(1,0,vec.size()-1);
    for(int i=1;i<=n;i++){
        int L=find(w[i]-k+1)-1,R=find(w[i]+k);
        int c=max(query(1,0,L), query(1,R,vec.size()-1));
        modify(1,find(w[i]),c+1);
    }
    printf("%d\n", query(1,0,vec.size()-1));
    return 0;
}

G(***)二分 + spfa/貪心(沒寫)

當更新完某條邊後,則兩個端點的度數也會發生變化
所以可以用spfa通過變形來實現更新完然後再更新的效果

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 550;
bool dist[N][N];
bool d[N][N];
bool st[N];
int f[N],g[N];
int n,m;
bool spfa(int x){
    memcpy(d,dist,sizeof(dist));
    memcpy(f,g,sizeof(f));
    memset(st,0,sizeof(st));
    queue<int>que;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(!d[i][j]&&f[i]+f[j]>=x){
                d[i][j]=d[j][i]=true;
                f[i]++;f[j]++;
                if(!st[i])que.push(i),st[i]= true;
                if(!st[j])que.push(j),st[j]= true;
            }
        }
    }
    while(que.size()){
        int i=que.front();que.pop();
        st[i]= false;
        for(int j=1;j<=n;j++){
            if(!d[i][j]&&f[i]+f[j]>=x){
                d[i][j]=d[j][i]=true;
                f[i]++;f[j]++;
                if(!st[i])que.push(i),st[i]= true;
                if(!st[j])que.push(j),st[j]= true;
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(f[i]!=n-1)return false;
    }
    return true;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)dist[i][i]= true;
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        g[a]++;g[b]++;
        dist[a][b]=dist[b][a]=true;
    }
    int l=1,r=2*n;
    while(l<r){
        int mid=l+r+1>>1;
        if(spfa(mid))l=mid;
        else r=mid-1;
    }
    printf("%d\n",l);
    return 0;
}

D(***)揹包/搜尋

我們可以將好感度相減,則可得到兩者最終好感度的差值(最終答案需要保證差值最小)
然後將好感動相加,則可得到兩則最終好感動的總和(用來判斷是否大於s)
所以我們可以將差值當作物品大小,好感度之和當作物品價值,通過n、Ai、Bi算出揹包大小
因為好感度差值可能為負數,所以加上一個大數使這個區間全為正數
通過揹包即可得到答案

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 12000+15,M = 6005;
int n,m;
int dp[33][N];
bool st[33][N];
int main(){
    scanf("%d%d",&n,&m);
    memset(dp,-0x3f,sizeof(dp));
    dp[0][M]=0;st[0][M]= true;
    for(int i=1;i<=n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        int w=a-b,v=a+b;
        for(int j=0;j<N;j++){
            dp[i][j]=max(dp[i-1][j],dp[i][j]);
            if(!(i==1&&j==M))st[i][j]=st[i][j]||st[i-1][j];
            int c=j+w;
            if(c>=0&&c<N){
                dp[i][c]=max(dp[i][c],dp[i-1][j]+v);
                st[i][c]=st[i][c]||st[i-1][j];
            }
        }
//        for(int j=0;j<=10;j++){
//            printf("%d %d\n",dp[i][M+j],dp[i][M-j]);
//        }
//        puts("");
    }
    for(int i=0;i<=M;i++){
//        printf("%d %d\n",dp[n][M+i],dp[n][M-i]);
        if(dp[n][M+i]>m&&st[n][M+i]||dp[n][M-i]>m&&st[n][M-i]){
            printf("%d\n",i);
            return 0;
        }
    }
    printf("%d",-1);
    return 0;
}

E(***)字首和

某個點對應兩條直線,對應這兩條直線我們可以寫出直線方程(相似做法在八皇后問題中出現)

  • x + y + c = 0
    • x + y = c
  • x - y - c = 0
    • x - y = c

因此我們可以將兩條直線壓縮成一個點存入兩個陣列中(只要判斷直線存不存在即可)
因為斜率相同的直線之間時沒有交點
所以需要考慮兩條直線的交點
總數即為 陣列1中點的個數 + 陣列2中點的個數 - 相交的點
列舉第二個陣列中的直線,可以得到直線與棋盤邊界的兩個交點

  • 通過兩個交點我們可以對映成陣列一中的兩個值,也就是一個區間,檢視區間中有多少個交點(字首和維護即可)
    注意因為交點可能不是整數點,所以我們需要隔二做字首和
    例如
    2 2
    1 1
    1 2
    答案應為4
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6+5,M=1e6;
typedef long long ll;
int n,m;
ll res=0;
ll tr[N*2];
bool d1[2*N],d2[2*N];
int get_1(int x){
    int d=1+n,ans=n;
    return ans-abs(d-x);
}
int get_2(int x){
    int d=0,ans=n;
    return ans-abs(d-x);
}
int main(){
    scanf("%d%d",&n,&m);
    while(m--){
        int x,y;
        scanf("%d%d",&x,&y);
        d1[x+y]=true;d2[x-y+M]=true;
    }
    for(int i=2;i<=n+n;i++){
        tr[i]=tr[i-2];
        if(d1[i]){
            res+= get_1(i);
            tr[i]++;
        }
    }
    for(int i=1-n;i<=n-1;i++){
        if(!d2[i+M])continue;
        if(i<=0){
            int L=2-i,R=2*n+i;
            res+= get_2(i)-tr[R]+tr[L-2];
        }
        else{
            int L=i+2,R=2*n-i;
            res+= get_2(i)-tr[R]+tr[L-2];
        }
    }
    printf("%lld\n",res);
    return 0;
}

F(****)樹狀陣列 + vector插入/平衡樹

不詳細解釋,想解的直接看程式碼

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<list>
using namespace std;
const int N = 5e5+5;
int tr[N],n,idx;
//每一段有哪些人
vector<int>lis[N];
//每一段的編號
vector<int>vec[N];
int lowbit(int x){
    return x&-x;
}
void add(int x){
    for(int i=x;i<N;i+= lowbit(i)){
        tr[i]++;
    }
}
int query(int x){
    int res=0;
    for(int i=x;i;i-= lowbit(i)){
        res+=tr[i];
    }
    return res;
}
int get_back(int x,vector<int>v,int sum){
    //前面有多少人 一共有前面sum-1個人
    int d= query(v[x]);
    return sum-1-d;
}
void pushback(int c,int i){
    ++idx;
    vec[c].push_back(idx);
    lis[idx].push_back(i);//這一段裡面有哪些人
    add(idx);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int c,a;
        scanf("%d%d",&c,&a);
        if(vec[c].empty()){
            //沒地方可插隊
            pushback(c,i);
        }
        else{
            int l=0,r=vec[c].size()-1;
            while(l<r){
                int mid=l+r>>1;
                if(get_back(mid,vec[c],i)<=a)r=mid;
                else l=mid+1;
            }
            int people= get_back(l,vec[c],i);
            if(people<=a){
                //有地方可以插隊
                int p_id=vec[c][l];
                int d=max((int)lis[p_id].size()-(a-people),0);
                add(p_id);lis[p_id].insert(lis[p_id].begin()+d,i);
            }
            else{
                //沒地方可插隊
                pushback(c,i);
            }
        }
    }
    bool flag= false;
    for(int i=1;i<=idx;i++){
        for(auto c:lis[i]){
            if(flag)printf(" ");
            printf("%d",c);
            flag=true;
        }
    }
    return 0;
}