目录
A.二十四点*
这题24点的规则不需要用完所有的数字,所以只要子序列能计算出24点该序列就满足条件。
一共就两个数据,第一个样例,第二个数据1~10,答案是891。
给状压开一个set存储所有能计算的数字,更新的时候枚举子状态和子状态存储的值,进行加减乘除运算。
会存在一些恶心的数据需要用到小数,所以要用到分数的处理。场上没有考虑这种,写了个不整除就跳过也能过,也非常快。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+2e2;
int lcm(int x,int y){
return x*y/__gcd(x,y);
}
struct Fen{
int fz,fm;
Fen(){fz=0,fm=0;}
Fen(int fz,int fm):fz(fz),fm(fm){}
void operator = (const Fen o){
fz=o.fz;fm=o.fm;
}
bool operator == (const Fen o)const{
return fz==o.fz&&fm==o.fm;
}
bool operator < (const Fen o)const{
int lc=lcm(fm,o.fm);
return lc/fm*fz<lc/o.fm*o.fz;
}
Fen operator + (const Fen o){
Fen p=Fen(fz,fm); int lc=lcm(p.fm,o.fm); p.fz=lc/p.fm*p.fz+lc/o.fm*o.fz; p.fm=lc;
int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
}
Fen operator - (const Fen o){
Fen p=Fen(fz,fm); int lc=lcm(p.fm,o.fm); p.fz=lc/p.fm*p.fz-lc/o.fm*o.fz; p.fm=lc;
int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
}
Fen operator * (const Fen o){
Fen p=Fen(fz,fm); p.fz*=o.fz; p.fm*=o.fm;
if(p.fz==0||p.fm==0) return p;
int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
}
Fen operator / (const Fen o){
Fen p=Fen(fz,fm); p.fz*=o.fm; p.fm*=o.fz;
if(p.fz==0||p.fm==0) return p;
int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
}
};
set<Fen>dp[N];
int main()
{
int n,x;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&x);
dp[1<<i].insert(Fen(x,1));
}
Fen tg=Fen(24,1);
int cnt=0;
for(int s=0;s<(1<<n);s++){
bool flag=false;
for(int i=s&(s-1);i;i=(i-1)&s){
int j=s-i;
set<Fen>::iterator it,jt;
for(it=dp[i].begin();it!=dp[i].end();it++){
for(jt=dp[j].begin();jt!=dp[j].end();jt++){
Fen ans;
Fen ta=*it;
Fen tb=*jt;
if(ta==tg) flag=true,dp[s].clear(),dp[s].insert(ta);
if(tb==tg) flag=true,dp[s].clear(),dp[s].insert(tb);
if(flag==true) break;
ans=ta+tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
ans=ta-tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
ans=tb-ta; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
ans=ta*tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
if(tb.fz!=0){ans=ta/tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);}
if(ta.fz!=0){ans=tb/ta; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);}
if(flag) break;
}
if(flag) break;
}
}
if(flag) cnt++;
}
printf("%d\n",cnt);
}
/*
10
1 2 3 4 5 6 7 8 9 10
891
*/
B.集合
分两种情况:
- 如果两点穿过圆,那么两点做切线,切线长度加上切点在圆上的距离。
- 如果两点不穿过圆,那么要求两点在圆上的反射点。因为反射角等于入射角,就在两点与圆心连接的范围内二分角度即可。
剩下的就是板子的事了。我在写的时候写了个判断条件,如果圆心到两点组成的线段距离等于半径就直接输出两点之间距离,这样一直wa。然后改成如果圆心到其中一点距离等于半径就输出两点之间距离,这样就过了,也不知道为什么。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const db pi=acos(-1);
const db eps=1e-8;
int sign(db k){if(k>eps) return 1; if(k<-eps) return -1; return 0;}
int cmp(db k1,db k2){return sign(k1-k2);}
struct point{
db x,y;
point operator + (const point o){return (point){x+o.x,y+o.y};}
point operator - (const point o){return (point){x-o.x,y-o.y};}
point operator * (const db o){return (point){x*o,y*o};}
point operator / (const db o){return (point){x/o,y/o};}
point trun(db k){return (point){x*cos(k)-y*sin(k),x*sin(k)+y*cos(k)};}
db abs(){return sqrt(x*x+y*y);}
db abs2(){return x*x+y*y;}
}a,b;
struct circle{
point o;db r;
}c;
db dot(point k1,point k2){return k1.x*k2.x+k1.y*k2.y;}
db cross(point k1,point k2){return k1.x*k2.y-k1.y*k2.x;}
point proj(point k1,point k2,point p){
point k=k2-k1;return k1+k*dot(p-k1,k)/k.abs2();
}
db disPP(point p1,point p2){return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));}
db getAngle(point k1,point k2){return fabs(atan2(cross(k1,k2),dot(k1,k2)));}
point getPC(point p,circle c){return c.o+(p-c.o)*(c.r/disPP(c.o,p));}
bool inmid(db k1,db k2,db k3){return sign(k1-k3)*sign(k2-k3)<=0;}
bool inmid(point p1,point p2,point p3){return inmid(p1.x,p2.x,p3.x)&&inmid(p1.y,p2.y,p3.y);}
db disSP(point k1,point k2,point p){
point k3=proj(k1,k2,p);
if(inmid(k1,k2,k3)) return disPP(k3,p);
return min(disPP(k1,p),disPP(k2,p));
}
int clockwise(point k1,point k2,point k3){
return sign(cross(k2-k1,k3-k1));
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%lf%lf",&a.x,&a.y);
scanf("%lf%lf",&b.x,&b.y);
scanf("%lf%lf%lf",&c.o.x,&c.o.y,&c.r);
db cp=cmp(disSP(a,b,c.o),c.r);
//if(cp==0) printf("%.3f\n",disPP(a,b));
if(cp>=0){
if(cmp(disPP(a,c.o),c.r)==0||cmp(disPP(b,c.o),c.r)==0){
printf("%.3f\n",disPP(a,b));
continue;
}
db lo=0,hi=getAngle(a-c.o,b-c.o);
if(clockwise(c.o,a,b)==-1) swap(a,b);
point base=(a-c.o)*c.r/disPP(a,c.o);
while(1){
db mid=(lo+hi)/2;
point mp=(base).trun(mid)+c.o;
int tmp=cmp(getAngle(a-mp,mp-c.o),getAngle(b-mp,mp-c.o));
if(tmp==0){
printf("%.3f\n",disPP(a,mp)+disPP(b,mp));
break;
}
if(tmp>0) hi=mid;
else lo=mid;
}
}
else{
db d1=disPP(a,c.o),d2=disPP(b,c.o);
db angle=getAngle(a-c.o,b-c.o);
angle-=acos(c.r/d1)+acos(c.r/d2);
db ans=sqrt(d1*d1-c.r*c.r)+sqrt(d2*d2-c.r*c.r)+angle*c.r;
printf("%.3f\n",ans);
}
}
}
D.精简改良
同样是状压,表示状态为
,以
为根的答案。这种状态就意味着
里面除了
,其他点不会再连出去边了。
然后利用树形dp的思想更新,枚举子区间以
为根,另一个子区间
就是
,枚举
的根
,更新方程如下:
dp[s][u]=max(dp[s][u],dp[su][u]+dp[sv][v]+1ll*num[sv]*(n-num[sv])*mp[u][v]);
会有点卡常,最好预处理出状态的每个点。还有对于非法状态就不要更新了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+7e3;
ll dp[N][20];
int num[N];
int n,m;
int u,v,w;
vector<int>pnt[N];
bool flag[N];
int mp[20][20];
int main()
{
scanf("%d%d",&n,&m);
for(int s=1;s<(1<<n);s++){
num[s]=num[s&(s-1)]+1;
for(int i=1;i<=n;i++){
if(s&(1<<(i-1))){
if(num[s]==1) dp[s][i]=0;
else dp[s][i]=-1;
pnt[s].push_back(i);
}
}
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
mp[u][v]=mp[v][u]=w;
}
for(int s=1;s<(1<<n);s++){
for(int su=(s-1)&s;su;su=(su-1)&s){
int sv=s-su;
int sz_u=pnt[su].size();
int sz_v=pnt[sv].size();
bool flag=true;
for(int iu=0;iu<sz_u;iu++){
int u=pnt[su][iu];
if(dp[su][u]==-1) break;
for(int iv=0;iv<sz_v;iv++){
int v=pnt[sv][iv];
if(dp[sv][v]==-1){flag=false;break;}
if(mp[u][v]==0) continue;
dp[s][u]=max(dp[s][u],dp[su][u]+dp[sv][v]+1ll*num[sv]*(n-num[sv])*mp[u][v]);
}
if(flag==false) break;
}
}
}
ll ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[(1<<n)-1][i]);
}
printf("%lld\n",ans);
}
F.小清新数论*
莫比乌斯,两个分块,复杂度为O(N)。
里外两层都有,所以都可以分块。队友代码
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e7+9;
const LL mod = 998244353;
LL u[N];
LL pri[N>>1],now;
bool vis[N];
void init(){
vis[1]=1;u[1]=1;
for(int i=2;i<N;i++){
if(!vis[i])pri[++now]=i,u[i]=-1;
for(int j=1;j<=now&&pri[j]*i<N;j++){
vis[pri[j]*i]=1;
u[pri[j]*i]=-u[i];
if(i%pri[j]==0){u[pri[j]*i]=0;break;}
}
}
for(int i=1;i<N;i++)u[i]=u[i]+u[i-1];
}
LL solve(LL n){
LL ans=0;
LL NN=n;
for(LL l=1,r;l<=NN;l=r+1){
r=(n/(n/l));
LL re=(u[r]-u[l-1])*(n/l)%mod*(n/l)%mod;
ans=(ans+re)%mod;
}
return (ans+mod)%mod;
}
int main(){
init();
LL n;
while(cin>>n){
LL NN=n;
LL ans=0;
for(LL l=1,r;l<=NN;l=r+1){
r=NN/(NN/l);
LL d=(l+r)*(r-l+1)/2%mod;
d=(u[r]-u[l-1]+mod)%mod;
LL re=solve(n/l);
//printf("re:%lld\n",re);
re=(re+mod)%mod;
ans=(ans+d*re%mod)%mod;
//printf("ans:%lld\n",ans);
}
printf("%lld\n",ans);
}
}
G.排列
模拟,队友代码
#include <iostream>
#include<algorithm>
#include<stdio.h>
#include<vector>
#include<map>
using namespace std;
int main(){
int a[100005];
int b[100005];
int c[100005];
int n;
while(~scanf("%d",&n))
{
int last=1e9;
int cnt=0;
int x;
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
if(x<last)a[x]=++cnt;
else a[x]=cnt;
last=x;
}
last=0;
for(int i=1;i<=n;i++)
{
if(a[i]!=last)b[i]=a[i];
else b[i]=++cnt;
last=a[i];
}
for(int i=1;i<=n;i++)printf("%d ",b[i]);
printf("\n");
}
}
H.涂鸦*
每一个坐标为的点被染成黑色的概率是
,所以只要把在矩形外的所有概率累加即可。
对于染白操作可以用差分约束维护,矩形的两个对角线分别打上+1和-1的标记,对于前缀和为0的点就一定在矩形之外,也就对答案有贡献。
#include<iostream>
#include<stdio.h>
using namespace std;
typedef long long ll;
const ll N=1e3+7;
const ll mod=998244353;
ll a[N][N],c[N][N],f[N];
ll qpow(ll x,ll y){
ll res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod;y>>=1;
}
return res;
}
int main()
{
ll n,m,q;
scanf("%lld%lld%lld",&n,&m,&q);
for(ll i=1,l,r;i<=n;i++){
scanf("%lld%lld",&l,&r);
for(ll j=1;j<=m;j++){
if(j>=l&&j<=r){
a[i][j]=(a[i][j]+(j-l+1)*(r-j+1)%mod*2%mod*qpow((r-l+1)*(r-l+2)%mod,mod-2)%mod)%mod;
}
}
}
for(ll i=1,lx,ly,rx,ry;i<=q;i++){
scanf("%lld%lld%lld%lld",&lx,&ly,&rx,&ry);
c[lx][ly]++;
c[rx+1][ry+1]++;
c[rx+1][ly]--;
c[lx][ry+1]--;
}
ll ans=0;
for(ll i=1;i<=n;i++){
for(ll j=1;j<=n;j++){
c[i][j]+=c[i-1][j]+c[i][j-1]-c[i-1][j-1];
if(c[i][j]==0){
ans=(ans+a[i][j])%mod;
}
}
}
printf("%lld\n",ans);
}
I.石头剪刀布
因为题目保证了数据的合法性,也就是说不存在答案为0的情况。所以如果已知每个人的主场次数和客场次数
,答案就会变成
画一下图我们可以发现这是一张树,我们对这个可以用并查集来维护。维护两个值:总数a,主场次数b。假设有操作1 x y,就把y连向x,并做如下两个更新操作。
- a[y]-=a[x],a[x]++;
- b[x]++,b[y]-=b[x];
询问的时候直接对节点到根这条路径上的值进行累加。
可以对并查集按秩合并,这样既要考虑两种合并的不同更新。个人认为也可以直接路径压缩,压缩的点只要减去新的根节点的值就行了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=998244353;
const ll N=2e5+7;
ll fa[N],a[N],b[N],rk[N];
ll n,m,k,x,y;
ll qpow(ll x,ll y){
ll res=1;
while(y){
if(y&1) res=x*res%mod;
x=x*x%mod,y>>=1;
}
return res;
}
struct Node{
ll fa,aa,bb;
};
Node fd(ll x){
if(x==fa[x]) return (Node){x,a[x],b[x]};
Node tmp=fd(fa[x]);
Node res=tmp;
res.aa+=a[x];res.bb+=b[x];
fa[x]=tmp.fa;
a[x]+=tmp.aa-a[tmp.fa];
b[x]+=tmp.bb-b[tmp.fa];
return res;
}
ll un(ll x,ll y){
x=fd(x).fa,y=fd(y).fa;
fa[y]=x;
a[y]-=a[x],a[x]++;
b[x]++,b[y]-=b[x];
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=n;i++) rk[i]=1,fa[i]=i;
for(ll i=1;i<=m;i++){
scanf("%lld",&k);
if(k==1){
scanf("%lld%lld",&x,&y);
un(x,y);
}
else{
scanf("%lld",&x);
Node tmp=fd(x);
ll ans=qpow(3,n)*qpow(2,tmp.bb)%mod*qpow(qpow(3,tmp.aa),mod-2)%mod;
printf("%lld\n",ans);
}
}
}