前言:
渐入佳境233
JZOJ 5771 遨游
题目
一个双向图,找出一对数 [ L , R ] , L ≤ R [L,R],L\leq R [L,R],L≤R,使双向图从起点到终点的边权在该区间范围内,且使 L L L尽量大, R R R尽量小
分析
可以发现这是一道有关最大和最小的题目,then就想到了二分答案,二分下界,用最小生成树kruskal判断最长边的长度,若能形成最小生成树,那么记录真正的上界和下界,kruskal使用并查集
代码
#include <cstdio>
#include <deque>
#include <algorithm>
#define rr register
#define N 50000
using namespace std;
struct node{int x,y; double w; int next;}e[N<<1|1];
int n,m,s,t,ls[N|1],tot,g[5001],f[N|1],lim,rim;
inline int in(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
inline int getf(int u){return f[u]==u?u:f[u]=getf(f[u]);}//路径压缩
inline void pd(int x){
double dow=2e9;
for (rr int i=1;i<=tot;++i) f[i]=i;
for (rr int i=1;i<=m;++i){
if (e[i].w<x) continue;
int fa=getf(e[i].x),fb=getf(e[i].y);
dow=dow<e[i].w?dow:e[i].w;//求下界
if (fa!=fb){
int fc=fa<fb?fa:fb;
f[fc]=fa+fb-fc;//并查集按秩合并
if (getf(s)==getf(t)){
lim=dow; rim=e[i].w+0.9; //求上下界
return;
}
}
}
return;
}
inline bool cmp(node a,node b){return a.w<b.w;}
signed main(){
n=in(); m=in(); rr int l=0,r=0;
for (rr int i=1;i<=m;++i){
rr int x=in(); ls[x]=i;
e[i]=(node){x,in(),in(),ls[x]};//建图
}
for (rr int i=1;i<=n;++i){
rr int t=in(); tot+=t;
while (t--) f[in()]=i;//记录省份
}
for (rr int i=1;i<=n;++i) g[i]=in();
for (rr int i=1;i<=m;++i){
if (f[e[i].x]==f[e[i].y]) e[i].w*=g[f[e[i].x]]/100.0;//优惠价
else e[i].w*=(g[f[e[i].x]]+g[f[e[i].y]])/200.0;
r=r>e[i].w+1?r:e[i].w+1;//上界剪枝
}
s=in(); t=in(); sort(e+1,e+1+m,cmp);//kruskal需要快排
while (l<r){
rr int mid=(l+r)>>1; pd(mid);//判断是否形成最小生成树
if (f[s]==f[t]) l=mid+1; else r=mid;
}
return !printf("%d %d",lim,rim);//输出上下界
}
JZOJ 5772 今天你AK了吗?
题目
求1到 n n n全排列字典序第k小
分析
k
=
x
1
(
n
−
1
)
!
+
x
2
(
n
−
2
)
!
+
⋯
+
x
n
−
1
k=x_1(n-1)!+x_2(n-2)!+\cdots+x_{n-1}
k=x1(n−1)!+x2(n−2)!+⋯+xn−1
所以可以想到逆康拓展开,因为求的是第k小,所以先要减掉1,然后答案根据证明也就是k除以1到
n
!
n!
n!的商,之后二分
x
1
,
x
2
,
…
,
x
n
−
1
,
x
n
x_1,x_2,\dots,x_{n-1},x_n
x1,x2,…,xn−1,xn的答案,用树状数组维护是否用过即可
代码
#include <cstdio>
#include <cstring>
#define rr register
#define mod (ll)(1e15)
typedef long long ll;
ll n,a[1335]; int b[100001],c[100001];
void print(int ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
rr char u=getchar(); a[0]=1; rr int len=0;
while (u<48||u>57) u=getchar(); rr ll k=1;
while (u>47&&u<58) n=(n<<3)+(n<<1)+u-48,u=getchar();
while (u<48||u>57) u=getchar();
while (u>47&&u<58) b[++len]=u-48,u=getchar();
for (rr int i=len,j=0;i>=1;i--){//压位
j++; a[a[0]]+=b[i]*k; k=(k<<1)+(k<<3);
if (j==15) j=0,k=1,a[++a[0]]=0;
}a[len=1]--;
while (a[len]<0) a[++len]--,a[len-1]+=mod;
while (a[0]>1&&!a[a[0]]) a[0]--; b[n]=0;
for (rr int i=2;i<=n;i++){//高精除单精求余数
rr ll g=0;
for (rr int j=a[0];j;j--){
a[j]+=g*mod;
g=a[j]%i; a[j]/=i;
}
b[n-i+1]=g;
while (a[0]>1&&!a[a[0]]) a[0]--;
}
for (rr int i=1;i<=n;i++) c[i]=i&-i;//优化,初始化树状数组O(n)
for (rr int i=1;i<=n;i++){
rr int l=1,r=n;
while (l<r){//二分查找
rr int mid=l+r>>1;
rr int sum=0,x=mid;
while (x) sum+=c[x],x-=x&-x;//求1到mid的个数
if (sum<=b[i]) l=mid+1; else r=mid;
}
print(l); putchar(' ');
while (l<=n) c[l]--,l+=l&-l;//用掉了这个数在树状数组减掉
}
return 0;
}
JZOJ 5773 简单数学题
题目
给定 n n n,求 n − 1 2 T n − T \frac{n-\frac{1}{2}T}{n-T} n−Tn−21T中 T T T的正整数解
分析
作为本次比赛最简单的一道数论题,大概讲讲证明就行了
设
x
=
n
−
T
x=n-T
x=n−T,那么原式=
n
−
1
2
(
n
−
x
)
x
=
n
2
x
+
1
2
\frac{n-\frac{1}{2}(n-x)}{x}=\frac{n}{2x}+\frac{1}{2}
xn−21(n−x)=2xn+21
由于原式的结果是正整数,
2
∗
原
式
=
n
x
+
1
2*原式=\frac{n}{x}+1
2∗原式=xn+1
所以需要保证
n
m
o
d
  
x
=
0
且
n
m
o
d
  
x
n \mod x=0且n \mod x
nmodx=0且nmodx是奇数
那么就可以在
O
(
n
)
O(\sqrt n)
O(n)的时间解决
代码
#include <cstdio>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
ll n; queue<ll>q1; stack<ll>q2;
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
scanf("%lld",&n);
for (register ll i=1;i<n&&i*i<=n;++i)
if (n%i==0){
if ((n/i)&1) q2.push(n-i);//当然要用n减回去
if (i>1&&i&1) q1.push(n-n/i);
}
print(q1.size()+q2.size());
while (q1.size()) putchar(' '),print(q1.front()),q1.pop();
while (q2.size()) putchar(' '),print(q2.top()),q2.pop();
return 0;
}