NOIP 2013
- Prob.1 轉圈遊戲
- Prob.2 火柴排隊
找到循環節,然後快速冪。
代碼:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int pos[1000005],vis[1000000];
int n,m,k,x,p,mod;
int pow(int a,int b){
int now=1;
while(b){
if(b&1) now=1ll*now*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return now;
}
int main(){
scanf("%d%d%d%d",&n,&m,&k,&x);
while(!vis[x]){
vis[x]=1; pos[mod++]=x;
x=(x+m)%n;
}
p=pow(10,k);
printf("%d",pos[p]);
return 0;
}
騷操作啊。我太菜了。
首先,(由於每一列的高度互不相同),我們把每一列分別按高度從小到大的順序給以每個元素排名,
那麽最小的距離和就是 兩列中相同的排名兩兩對應 所計算出來的值。
證明的話,可以考慮交換兩個組合的元素,發現計算出的權值只會變大。
為了把相同的名次對應,
所以就是要把兩個序列都從小到大排序或者都從大到小排序麽?
蠢蠢的我想到了上面這個東西,但顯然錯了,然後我就不知道如何搞了。
正解:
其實兩列的交換是等價且相互的
也就是說,我們可以只通過對第一列進行交換達到相同名次對應的目的。
所以我們映射出第一個序列的每個元素在第二個序列中的出現的位置,得到一個新的序列,
求出這個新的序列的逆序對數就是答案了。
(這個題太強辣。%%%)
代碼:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 100005 using namespace std; const int mod=99999997; int cc[MAXN],A[MAXN],B[MAXN],p[MAXN]; int n,ans; void readwork(int *a){ for(int i=1;i<=n;i++) scanf("%d",&a[i]),cc[i]=a[i]; sort(cc+1,cc+n+1); for(int i=1;i<=n;i++) a[i]=lower_bound(cc+1,cc+n+1,a[i])-cc; } void merge(int *a,int l,int r){ static int tmp[MAXN]; if(l==r) return; int mid=(l+r)>>1; merge(a,l,mid); merge(a,mid+1,r); int h=l,t=mid+1,pp=l; while(h<=mid&&t<=r){ if(a[h]<a[t]) tmp[pp++]=a[h++]; else ans=(1ll*ans+mid-h+1)%mod,tmp[pp++]=a[t++]; } while(h<=mid) tmp[pp++]=a[h++]; while(t<=r) tmp[pp++]=a[t++]; for(int i=l;i<=r;i++) a[i]=tmp[i]; } int main(){ scanf("%d",&n); readwork(A); readwork(B); for(int i=1;i<=n;i++) p[B[i]]=i; for(int i=1;i<=n;i++) A[i]=p[A[i]]; merge(A,1,n); printf("%d",ans); return 0; }
先是最大生成樹,保證了路徑的最小值最大;
然後在倍增,進行樹上RMQ就好了。
代碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
struct Cm_edge{
int u,v,w;
bool operator <(const Cm_edge &rtm){
return w>rtm.w;
}
}E[50005];
struct Lk_edge{
int to,val,next;
}e[20005];
int head[10005],fa[10005],dep[10005],stm[10005][15],stf[10005][15];
int n,m,Q,ent=2;
void add(int u,int v,int w){
e[ent]=(Lk_edge){v,w,head[u]};
head[u]=ent++;
}
void cmin(int &a,int b){
if(a>b) a=b;
}
int find(int u){
return u==fa[u]?u:fa[u]=find(fa[u]);
}
void dfs(int u,int dad,int val){
stf[u][0]=dad; stm[u][0]=val; dep[u]=dep[dad]+1;
for(int k=1;k<=14;k++){
stf[u][k]=stf[stf[u][k-1]][k-1];
if(!stf[u][k]) break;
stm[u][k]=min(stm[u][k-1],stm[stf[u][k-1]][k-1]);
}
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==dad) continue;
dfs(v,u,e[i].val);
}
}
int query(int u,int v){
if(find(u)!=find(v)) return -1;
int ans=INF;
if(dep[u]>dep[v]) swap(u,v);
for(int k=14;k>=0;k--){
if(dep[stf[v][k]]<dep[u]) continue;
cmin(ans,stm[v][k]); v=stf[v][k];
}
if(u==v) return ans;
for(int k=14;k>=0;k--){
if(stf[u][k]==stf[v][k]) continue;
cmin(ans,stm[u][k]); cmin(ans,stm[v][k]);
u=stf[u][k]; v=stf[v][k];
}
cmin(ans,stm[u][0]); cmin(ans,stm[v][0]);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
sort(E+1,E+m+1);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,u,v,w,fu,fv;i<=m;i++){
u=E[i].u,v=E[i].v,w=E[i].w;
fu=find(u); fv=find(v);
if(fu==fv) continue;
fa[fv]=fu;
add(u,v,w); add(v,u,w);
}
memset(stm,0x3f,sizeof(stm));
dfs(1,0,INF);
scanf("%d",&Q);
for(int i=1,u,v;i<=Q;i++){
scanf("%d%d",&u,&v);
printf("%d\n",query(u,v));
}
return 0;
}
思路很巧妙的題。
之前想的是維護出每層的連續的段數和。實現很麻煩,而且復雜度堪憂。
正解:
從左到右枚舉,考慮當前的高度 h[i] :
(可以想象成首先我們已經搭好了1~i-1,現在告訴你需要在第i列搭 h[i]的高度)
如果 h[i-1]>=h[i] 那麽顯然,在我們搭好 i-1那一列是可以順帶把 第i列搭好。
如果 h[i-1]<h[i] 那麽高度h[i]的下面部分,即1~h[i-1],也可以在搭 i-1那一列是可以順帶搭好。
剩下的 h[i]-h[i-1] 就要另外搭了,即貢獻答案, ans+=h[i]-h[i-1]。
代碼:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
int n,a=0,b=0,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
a=b; scanf("%d",&b);
if(a<b) ans+=b-a;
}
printf("%d",ans);
return 0;
}
求最大拐點數,貪心做就好。
考慮連續的三個數 a,b,c令 a<b 並且選了a,b
1).如果b<c,那麽不貢獻答案,並棄掉b,改為選c,這樣可以使得以後下降的範圍相比b更大。
2).如果b>c,那麽顯然該選擇c,一是可以貢獻一個答案(出現了拐點),二是使得以後上升的範圍相比b更大。
代碼:
#include<cstdio> #include<cstring> #include<iostream> #include<set> using namespace std; int n,k,now,pre,ans=1; int main(){ scanf("%d",&n); scanf("%d",&pre); for(int i=2;i<=n;i++){ scanf("%d",&now); if(now>pre&&k!=1) k=1,ans++; if(now<pre&&k!=2) k=2,ans++; pre=now; } printf("%d",ans); return 0; }
NOIP 2013