2022年TZC集訓隊省賽選拔賽1題解
阿新 • • 發佈:2022-03-06
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;
}