A Leftbest
给出一个数列,问对于ai前面的比他大的最小值的和。
用set upperbound一下就好了
int main(){
int n;
scanf("%d",&n);
long long ans = 0;
set<int> fk;
for(int i = 0; i < n; ++i){
int x;
scanf("%d",&x);
auto it = fk.upper_bound(x);
fk.insert(x);
if(it==fk.end())continue;
ans+=*it;
}
printf("%lld\n",ans);
}
B First Date
给出一个n个节点m条边的图,每条边都有一个x,y值,他的权值就是x+y*a。这个a是一个[0,1]平均分布。问从s到t的期望时间的最小值。
(这个题很神奇,a是平均分布,为什么a就不能直接等于1/2?)
迭代a从0到1的值,然后计算每一个a,s到t的最小时间。最后除以迭代次数得出平均值。
struct Edge{
int v;
int x,y;
};
vector<Edge> g[1000];
double dis[10000];
void bfs(int u,double a){
queue<int> q;
q.push(u);
dis[u] = 0;
while(!q.empty()){
int t = q.front();q.pop();
for(auto it : g[t]){
int v = it.v;
double w = it.x + a*it.y;
if(dis[v]==-1 || dis[v] > dis[t]+w){
dis[v] = dis[t]+w;
q.push(v);
}
}
}
}
int main(){
int n,m,s,t;
cin>>n>>m>>s>>t;
for(int i = 0; i <m; ++i){
int u,v,x,y;
cin>>u>>v>>x>>y;
g[u].pb({v,x,y});
g[v].pb({u,x,y});
}
double ans = 0;
for(double a = 0; a <=1; a+=0.00001) {
for(int i = 1; i <=n; ++i)dis[i]=-1;
bfs(s,a);
ans += dis[t];
}
printf("%.5lf\n",ans/100000);
}
C Sequence
要补吗???
D Capture Stars
给出两个内切圆,圆心在x轴且必过(0,0)。且给出m个点(x,y), 问另外一个夹在这两个圆之间的圆能最多包含对少个点(x,y)。
圆的反演(新知识啊)。圆的反演一般用在圆的外切内切的题。
反演定义:如果一个圆的圆心为0,圆心为r, 从圆心o拉出一条射线,对于圆上一点P,圆外一点, 如果
, 则这两个点互为反演。
因此该圆就可以变成一条直线了。
由于题目的两个圆过(0,0),而我们需要找一个共用的反演圆将这两个圆上的点都使用同一个反演圆反演到另外一个平面上。
反演圆圆心(0,0),半径为大圆直径2R。
由于圆的反演肯定是对于经过反演中心(0,0)和圆心的直线的垂线,因此对于大圆上一点,反演点
,
对于小圆上一点,反演点
,
对于平面上任意一点,反演点
, 有
同理
那么问题变成了有两条直线和
,一些点(x',y'),问有一个圆在两直线间,最多包含多少个点(x',y')
圆的中心肯定在两直线中间 ,圆的半径是
圆这条轨道上移动,求移动到某一个点时,覆盖的点最多。
求出每一个点的圆的覆盖范围 , 范围为
最后用加加减减求线段最多覆盖次数就可以了。
double sqr(double x) {
return x*x;
}
int main(){
int t;
cin>>t;
while(t--){
int n;
double R,r;
scanf("%d%lf%lf",&n,&R,&r);
double x0 = R*R/r+R;
double r0 = R*R/r-R;
vector< pair<double,int> > pos;
for(int i = 0; i < n; ++i){
double x,y;
cin>>x>>y;
double l = sqr(x)+sqr(y);
x = 4*R*R * x/l;
y = 4*R*R * y/l;
if(fabs(x-x0)<=r0){
double h = sqrt(sqr(r0) - sqr(x-x0));
pos.pb(mp(y-h,-1));
pos.pb(mp(y+h,1));
}
}
sort(pos.begin(), pos.end());
int ct = 0, ans = 0;
for(auto it :pos){
ct -= it.second;
ans = max(ans, ct);
}
printf("%d\n",ans);
}
}
E Triangulation
多边形三角剖分
由于题目是顺时针或者逆时针给出点,因此先全部统一为逆时针的点。
然后区间dp,对于dp[i][k],表示i连一条线到k,把i到k之间的点做一个三角剖分。
找一个在i,k之间的点j,由于是逆时针,因此j必须在i,k的右手边(p[i,k]*p[i,j]<0)。则有dp[i][k] += dp[i][j]*dp[j][k].
struct Point{
long long x,y;
void read(){
sf("%lld%lld",&x,&y);
}
Point operator -(const Point a){
Point ret;
ret.x = x-a.x;
ret.y = y-a.y;
return ret;
}
long long operator *(const Point a){
return x*a.y-y*a.x;
}
}p[300];
long long dp[300][300];
int main(){
int n;
cin>>n;
clr(dp);
long long s = 0;
fr(i,0,n){
p[i].read();
if(i)
s += (p[i]-p[0])*(p[i-1]-p[0]);
}
if(s>0){
for(int i = 0; i+i<n-1;++i)swap(p[i],p[n-i-1]);
}
fr(i,0,n){
dp[i][(i+1)%n]=1;
}
for(int l = 2; l<=n;++l)
for(int i = 0; i<n;++i){
int k = (i+l)%n;
for(int j = (i+1)%n;j!=k;j=(j+1)%n){
if((p[k]-p[i])*(p[j]-p[i])<0){
dp[i][k] = (dp[i][k] + dp[i][j]*dp[j][k])%mod;
}
}
}
printf("%lld\n",dp[0][n-1]);
}
F Points
签到题??
统计度数为1的点
int d[1000010];
int main(){
int n;
scanf("%d",&n);
for(int i = 0;i <n-1;++i){
int u,v;
scanf("%d%d",&u,&v);
d[u]++;d[v]++;
}
int ans = 0;
for(int i = 1; i <=n;++i)if(d[i]==1)++ans;
printf("%d\n",ans);
}
G ParallelNetworkAnalysis
要补吗??
H Graph
给出n个节点和几种操作(对某两个点连或删边,对某两个加进查询集或者从查询集删除)。
对于询问操作,问查询集中的每两个点是否在树上连通。
对于树上的增删边直接用LCT维护。
对于查询集是否连通,每加入两个点,则分配给该点一个比较大的随机值。如果查询集的点都连通,那么查询集的总异或值为0。 (为什么随机?我觉得是因为查询集的点数其实很少的,随机出一个很大的数才能防止不同的值异或后也为0,譬如1,2,3)
问题变成了LCT怎么维护整个树的异或值。在加入点到查询值的时候,直接修改树上节点的值。但是由于LCT维护的splay是一条链来的,pushup的时候不会统计非链的节点,因此需要多一个sum来统计。这个sum在link的时候被加进去。这样在splay做pushup的时候就可以一直往上滚了。
(不能直接把这个值加入到节点的值中,因为当access变成一条链的时候父节点和当前节点就拥有重复的值了)
access的时候,由于非链的节点变成链,需要对sum做修改(减去以前的,添加现在的)。
link的时候,也需要先对x,y做一个换根,保证连接的时候是根。
int son[N][2], f[N];
int sum[N],sum1[N],r[N];
int a[N];
int n,m;
int cnt;
void pushup(int x){
sum[x] = sum[son[x][0]] ^ sum[son[x][1]] ^ a[x] ^ sum1[x];
}
void switch_son(int x){
int t = son[x][0]; son[x][0] = son[x][1];son[x][1] = t;
r[x]^=1;
}
void pushdown(int x){
if(r[x]){
if(son[x][0])
switch_son(son[x][0]);
if(son[x][1])
switch_son(son[x][1]);
r[x] = 0;
}
}
bool isroot(int x){
return !(son[f[x]][0]==x || son[f[x]][1]==x);
}
void rotate(int x){
int y = f[x], z = f[y];
if(!isroot(y)){
son[z][son[z][1]==y] = x;
}
int p = son[y][1]==x;
int t = son[x][!p];
son[x][!p] = y;
son[y][p] = t;
f[x] = z; f[y] = x; if(t)f[t] = y;
pushup(y);
}
void splay (int x) {
//push down first
int y = x;
stack<int> st;
st.push(y);
while(!isroot(y)) st.push(f[y]),y=f[y];
while(!st.empty()) {
pushdown(st.top()),st.pop();
}
while(!isroot(x)){
int y = f[x];
int z = f[y];
if(!isroot(y)){
rotate(y);
}
rotate(x);
}
pushup(x);
}
void access(int x){
int y = 0;
while(x){
splay(x);
sum1[x] ^=sum[son[x][1]];
sum1[x] ^=sum[y];
son[x][1] = y; y = x;
pushup(x);
x = f[x];
}
}
void make_root(int x){
access(x);
splay(x);
switch_son(x);
}
int findroot (int x) {
access(x);
splay(x);
while(son[x][0])x = son[x][0];
return x;
}
void link(int x, int y){
make_root(x);
if(findroot(y)==x)return;
if(sum[x]){
cnt--;
}
if(sum[y]){
cnt--;
}
make_root(y);
f[y] = x;
sum1[x]^=sum[y];
make_root(x);
pushup(x);
if(sum[x]){
cnt++;
}
}
void cut(int x, int y){
make_root(x);
access(y);
// if(findroot(y)!=x) return;
splay(x);
if(f[y] != x || son[y][0]) return; //very tricky
if(sum[x]){
cnt--;
}
f[y] = 0;
son[x][1] =0;
splay(x);
if(sum[x]){
cnt++;
}
splay(y);
if(sum[y]){
cnt++;
}
}
void add(int x, int v){
make_root(x);
if(sum[x]){
cnt--;
}
a[x]^=v;
pushup(x);
if(sum[x]){
cnt++;
}
}
void add(int x, int y, int v){
add(x,v);
add(y,v);
}
int main(){
srand(time(0));
int t;
cin>>t;
while(t--){
sf("%d%d",&n,&m);
fr(i,0,n+1){
a[i] = 0;
clr(son[i]);
f[i] = 0;
sum[i] = 0;
r[i] = 0;
sum1[i] = 0;
}
cnt = 0;
map<pair<int,int>,int > v;
fr(i,0,m){
int op,x,y;
sf("%d",&op);
if(op!=5){
sf("%d %d",&x,&y);
if(x>y)swap(x,y);
}
if(op==1){
link(x,y);
}
else if(op==2){
cut(x,y);
}
else if(op==3){
v[mp(x,y)] = rand();
add(x,y,v[mp(x,y)]);
}
else if(op==4){
add(x,y,v[mp(x,y)]);
v[mp(x,y)] = 0;
}
else if(op==5){
if(cnt==0)printf("YES\n");
else printf("NO\n");
}
}
}
}
I Rooted Tree
我也不知道为什么,该序列是一个哈代-拉马努金拆分数列
把数列分解成奇数项和偶数项去dp求通项
然后就可以直接dp了
long long f[500010], g[500010];
long long dp[500010];
int mod = 998244353;
int main(){
int n;
scanf("%d",&n);
for(long long i = 1; i<=n;++i){
f[i] = (3ll*i*i-i)/2;
g[i] = (3ll*i*i+i)/2;
}
dp[0] = 1;
for(int i = 1; i<=n;i++){
for(int j = 1; f[j]<=i;j++){
if(j%2==1){
dp[i] += dp[i-f[j]];
if(g[j]<=i)dp[i]+= dp[i-g[j]];
}
else {
dp[i] -= dp[i-f[j]];
if(g[j]<=i)dp[i]-=dp[i-g[j]];
}
}
dp[i] %= mod;
if (dp[i] < 0) dp[i] += mod;
//printf("i = %d dp = %lld\n",i,dp[i]);
}
printf("%lld\n",dp[n-1]);
}
J Flowers
给出n种花,每种花有ai朵。问能凑成多少束花,每束花由m种不同的花组成。
二分枚举答案,对于需要组成k束花,则需要k*m朵。扫描每一种花判断可以最多给多少朵。
int a[300010];
int n,m;
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m;
long long sum = 0;
for(int i = 0; i < n; ++i){
scanf("%d",&a[i]);
sum += a[i];
}
auto check = [&](long long num){
long long tot = 0;
for(int i = 0; i< n; ++i){
tot += min((long long)a[i],num);
}
return tot>=num*m;
};
long long l = 0, r = sum/m+1;
long long ans = 0;
while(l+1<r){
ll mid = (l+r)>>1;
if(check(mid)){
l = mid;
}
else r = mid;
}
cout<<l<<endl;
}
}