写在前面的话:
这次比赛的题目把我惊艳到了,题目质量非常高,给出题人点个赞!
知识点:模拟。
参考cf难度:1500
这道题其实读懂题,按题意模拟就可以了。数据很小,所以每次操作直接排序即可。要注意的是每个学生能力值改变的时间点不要搞错了。
复杂度: O ( n 2 l o g n ) O(n^2logn) O(n2logn)
#include
using namespace std;
#define ll long long
struct node{
ll id,val,p;
};
node a[2000];
bool cmp(node a,node b){
if(a.val!=b.val)return a.val>b.val;
return a.id<b.id;
}
int main(){
int t,i;
int b;
int n,r,l;
cin>>n>>r>>l;
for(i=0;i<n;i++)cin>>a[i].id;
for(i=0;i<n;i++)cin>>a[i].val;
for(i=0;i<n;i++)cin>>a[i].p;
int len=n;
while(len>1){
sort(a,a+len,cmp);
for(i=0;i<10;i++)a[i].val+=r;
len=len/2+len%2;
if(len==1)break;
if(len>=6){
for(i=0;i<3;i++)a[i].val-=l;
}
for(i=0;i<len;i++)a[i].val+=a[i].p;
}
cout<<a[0].id<<" "<<a[0].val;
}
知识点:状压搜索/DFS
参考cf难度:2100
第一次想到的算法是贪心:枚举每个度数 i i i,先顺时针到 i i i再逆时针;或者先逆时针到 i i i再顺时针,结果wa了,甚至一度怀疑题目出问题了。
后面发现这个算法是错的,有可能出现先逆时针,再顺时针,再逆时针的情况(因为每个点的 t t t是不同的,所以不能简单的去贪心)。正确做法是枚举每一次选择顺时针或者逆时针,这样一共有 n n n次决策,总决策方案就是 2 n 2^n 2n种。
复杂度: O ( n ∗ 2 n ) O(n*2^n) O(n∗2n)
#include
using namespace std;
#define ll long long
double xa,ya,xb,yb,xc,yc,xd,yd;
struct node{
int w,v;
};
bool cmp(node a,node b){
return a.w<b.w;
}
node a[22];
ll tong[361];
int main(){
int t,i,j;
int b;
int n,r,l;
int x,y;
cin>>n>>x>>y;
for(i=0;i<n;i++){
ll w,v;
cin>>w>>v;
a[i].w=w;
a[i].v=v;
tong[w]=max(tong[w],v);
}
sort(a,a+n,cmp);
ll mi=1e12;
if(y<a[0].w){
l=n-1,r=0;
}
else{
for(i=0;i<n;i++){
if(y<a[i].w)break;
}
l=i-1,r=i%n;
}
// cout<
for(i=0;i<1<<n;i++){
int templ=l,tempr=r,st=y;
ll p=i,sum=0,ma=0;
for(j=0;j<n;j++){
if(p&1){
sum+=(st-a[templ].w+360)%360*x;
// cout<<"1:"<
ma=max(ma,sum+a[templ].v);
st=a[templ].w;
templ=(templ-1+n)%n;
}
else{
sum+=(a[tempr].w-st+360)%360*x;
// cout<<"2:"<
ma=max(ma,sum+a[tempr].v);
st=a[tempr].w;
tempr=(tempr+1)%n;
}
p/=2;
}
// cout<<"p"<
mi=min(mi,ma);
}
cout<<mi;
// cout<
}
知识点:前缀和预处理
参考cf难度:1600
这道题要求的是距离不大于k的所有01对的数量。那么对于每个0/1而言,只需要知道距离它不超过k的所有1/0的数量,然后前缀和预处理就可以O(1)查询了。
复杂度:O(n)
#include
using namespace std;
#define ll long long
ll sum1[500020],sum2[500050];
int main(){
int t,i;
string s;
ll n,c,k;
cin>>k;
cin>>s;
n=s.length();
ll s1=0,s2=0;
for(i=0;i<s.length();i++){
sum1[i+1]=s1+=s[i]=='0';
sum2[i+1]=s2+=s[i]=='1';
}
// for(i=1;i<=n;i++)cout<
ll cnt=0;
for(i=0;i<s.length();i++){
if(s[i]=='0'){
cnt+=sum2[min(i+k+1,n)]-sum2[max(i-k,0LL)];
// cout<
}
else{
cnt+=sum1[min(i+k+1,n)]-sum1[max(i-k,0LL)];
}
// cout<
}
cout<<cnt/2;
}
知识点:贪心
参考cf难度:800
签到题。很明显当且仅当摸大鱼体力不超过摸小鱼两倍的时候选择摸大鱼,否则一定会摸小鱼。注意如果 n n n是奇数那么最后要用小鱼补全。
复杂度 O ( T ) O(T) O(T)
#include
using namespace std;
#define ll long long
ll sum1[500020],sum2[500050];
int main(){
int t,i;
cin>>t;
while(t--){
ll n,a,b;
cin>>n>>a>>b;
if(a*2<=b)cout<<n*a<<endl;
else cout<<n/2*b+n%2*a<<endl;
}
}
知识点:计算几何/(三分)
参考cf难度:1900
这道题官方题解非常优秀,不过我是用三分卡过去的。
当两个点开始运动之后,很明显距离是先变小、后变大这一过程,满足可三分的性质。
由于double可能会卡精度,所以只要三分足够多次就可以了。
复杂度: O ( T l o g ? ) O(Tlog?) O(Tlog?)
#include
using namespace std;
#define ll long long
double xa,ya,xb,yb,xc,yc,xd,yd;
double f(double k){
double xf,yf;
if(ya<yb)yf=ya+k;
else yf=ya-k;
if(xc<xd)xf=xc+k;
else xf=xc-k;
return sqrt((xf-xa)*(xf-xa)+(yf-yc)*(yf-yc));
}
int main(){
int t,i;
int b;
int n,r,l;
cin>>t;
while(t--){
cin>>xa>>ya>>xb>>yb>>xc>>yc>>xd>>yd;
if(ya==yb){
swap(xa,xc),swap(ya,yc),swap(xb,xd),swap(yb,yd);
}
double mi=sqrt((xc-xa)*(xc-xa)+(ya-yc)*(ya-yc));
double len1=abs(yb-ya),len2=abs(xd-xc),cc=abs(len1-len2);
if(len1<len2)xd-=cc;
else yb-=cc;
// cout<
double l=0,r=min(len1,len2);
for(i=0;i<1000;i++){
double lmid=l+(r-l)/3,rmid=l+2*(r-l)/3;
if(f(lmid)<f(rmid))r=rmid;
else l=lmid;
}
printf("%.2f\n",f(l));
}
}
知识点:概率论
参考cf难度:1300
看到数据范围这么小,直接套期望的公式就可以了:
E = ∑ i = 1 n p ( i ) ∗ i E=\sum_{i=1}^n p(i)*i E=∑i=1np(i)∗i
其中 p ( i ) p(i) p(i)用古典概型的条件概率直接求就可以。
复杂度: O ( n ) O(n) O(n)
#include
using namespace std;
#define ll long long
ll sum1[500020],sum2[500050];
int main(){
int t,i;
int a,b;
cin>>a>>b;
double sum=a+b;
double p=0,res=0,sump=0;
for(i=1;i<=a+1;i++){
p=(1-sump)*b/sum;
sump+=p;
res+=i*p;
sum--;
}
printf("%.6f",res);
}
知识点:贪心
参考cf难度:1500
这道题其实很简单,不过由于读题劝退所以前2h居然没有一发提交hhh
题目的公式已经告诉你了,那么可以根据每个怪的属性求出最大的伤害,然后求出击杀次数,之后按次数从小到大选择就可以模拟了。
不过这道题出的不好的一点的是居然存在血量为0的怪,理论上无视它就可以了(
复杂度: O ( T n l o g n ) O(Tnlogn) O(Tnlogn)
#include
using namespace std;
#define ll long long
ll vis[1000100],prime[1000100],x=0,tong[1000100];
struct node{
int hp,d,val;
};
node a[100022];
bool cmp(node a,node b){
return a.val<b.val;
}
int main(){
// init();
int t,i,j;
// for(i=999990;i<=1000000;i++)cout<
// return 0;
int b;
int n,r,l,m;
cin>>t;
while(t--){
ll sum=0;
cin>>n>>m;
int c1,k1,c2,k2;
cin>>c1>>k1>>c2>>k2;
for(i=0;i<n;i++)cin>>a[i].hp;
for(i=0;i<n;i++)cin>>a[i].d;
for(i=0;i<n;i++){
int dmg=max(0,max(c1-k1*a[i].d,c2-k2*a[i].d));
if(dmg==0){
a[i].val=1e9;continue;}
// cout<
a[i].val=a[i].hp/dmg+(a[i].hp%dmg!=0);
}
sort(a,a+n,cmp);
for(i=0;i<n&&sum+a[i].val<=m;i++)sum+=a[i].val;
cout<<i<<endl;
}
}
知识点:思维
参考cf难度:1400
非常巧妙的一道题。这道题的关键就是构造出(或者猜出) n ≥ 2 n≥2 n≥2时最小面积一定是1。
具体的构造方法参考下图:
复杂度: O ( T ) O(T) O(T)
#include
using namespace std;
#define ll long long
int main(){
int t,i;
int b;
int n,r,l;
cin>>t;
while(t--){
double x;
cin>>x;
if(x==1)cout<<"0.5"<<endl;
else printf("%.1f\n",1.0);
}
}
知识点:dp、贪心
参考cf难度:2200
非常好的一道题。题意大概这样:将每个1变成0的条件是:这个1的前面都没有0。并且变完了之后会把这个1前面 k k k个字符都变成1。
因此可以得到dp方程:
d p [ i ] = 1 + ∑ j = 1 k d p [ i − j ] dp[i]=1+\sum_{j=1}^kdp[i-j] dp[i]=1+∑j=1kdp[i−j]
这个方程可以简化成这样:
前 k k k项dp项之和与 d p [ i − 1 ] dp[i-1] dp[i−1]也可以建立某种联系,经过推导后可以得出:
d p [ i ] = 2 ∗ d p [ i − 1 ] − d p [ i − k − 1 ] dp[i]=2*dp[i-1]-dp[i-k-1] dp[i]=2∗dp[i−1]−dp[i−k−1]
有 t t t次交换的机会,很明显的,一定优先交换右边的1,因为右边的1会带来更多的次数。
之后将预处理出的dp数组进行求和就可以了。
复杂度: O ( n + t ) O(n+t) O(n+t)
#include
using namespace std;
#define ll long long
int mod=1e9+7;
ll power(ll a,ll b){
ll res=1;
while(b){
if(b&1)res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
ll inv(ll x){
return power(x,mod-2);
}
struct node{
int l,r;
};
ll dp[100010];
int main(){
// init();
//cout<
int t,i,j,k;
// for(i=999990;i<=1000000;i++)cout<
// return 0;
int b;
int n,r,l,m;
int x,y;
cin>>n>>k>>t;
string s;
cin>>s;
dp[1]=1;
for(i=2;i<=k+1;i++){
dp[i]=dp[i-1]*2%mod;
}
for(i=k+2;i<=1e5;i++){
dp[i]=2*dp[i-1];
if(i>k+1)dp[i]-=dp[i-k-1];
dp[i]=(dp[i]+mod)%mod;
}
//for(i=1;i<=10;i++)cout<
while(t){
for(i=s.length()-1;i>=0;i--){
if(s[i]=='1')break;
}
for(;i>=0;i--)if(s[i]=='0')break;
if(i<0)break;
for(;i<s.length()-1;i++){
if(s[i+1]=='1')swap(s[i],s[i+1]),t--;
if(!t)break;
}
if(!t)break;
}
ll sum=0;
for(i=0;i<s.length();i++){
sum+=(s[i]=='1')*dp[i+1];
sum%=mod;
}
cout<<sum;
}
知识点:博弈
参考cf难度:1300
当n小于3时,显然后手胜。
n≥3时,一定先手胜。证明如下:
若n为奇数,第一次这样画:
后面每次对方怎么画,自己在对称的另一边画同样的形状即可。
若n为偶数,第一次这样画:
然后用同样的方式,对称着画,保证每次画完都是轴对称状态,这样第一次无法画的一定是对方。
复杂度: O ( T ) O(T) O(T)
#include
using namespace std;
#define ll long long
ll sum1[500020],sum2[500050];
int main(){
int t,i;
cin>>t;
while(t--){
ll n,a,b;
cin>>n;
if(n<3)cout<<"Hugin\n";
else cout<<"Steve\n";
}
}
知识点:数论,构造算法
参考cf难度:1900
初始序列为1,2,3……n。第一次某个数加1,第二次某个数加2,以此类推,求操作数最小的次数使得gcd大于1,并输出字典序最小的序列。
首先可以证明,最小的操作数为n-1。证明如下:
必要性:
若n是2的倍数,那么把所有数变成2的倍数需要n-1次。若n不是2的倍数,则需要n次。
若n%3=0,那么把所有数变成3的倍数需要n-1次。若n%3=1,那么无论如果不能让他们都变成3的倍数。若n%3=2,那么把所有数变成3的倍数需要n次。
一般地,若我们要让所有数的gcd变成p,首先要让1,…,p-1所有数变成p的倍数,然后遇到一个p的时候操作在模p意义下是无效的。所以至少需要n-1次。
充分性:
设有1,2…n的初始序列,我们让1加到n-1,2加到n-2……以此类推,n-1加到1上面,这样n-1次操作所有数都变成n了。
证明完毕。
所以这道题的关键是怎么找到字典序最小的n-1的序列。
假设我们要让所有数的gcd变成p,我们可以让1加到p-1上,2加到p-2上……p-1加到1上,这次第一个数变成p了,可以再让他加p(这样字典序最小);之后让p+1加到2p-1上……以此类推。最终我们的序列是p-1,p-2…1,1,2p-1,2p-2…p+1,1,3p-1…
那么如果让字典序最小,很明显就是让p最小。而p又必须是n的因子(这样才能n-1次操作完成),所以这道题只要求出n除了1以外最小的因子就可以了。这个操作可以通过线性筛进行预处理。
复杂度 O ( n ) O(n) O(n)
#include
using namespace std;
#define ll long long
ll vis[1000100],prime[1000100],x=0,tong[1000100];
void init(){
ll n=1e6,i,j;
for(i=2;i<=n;i++)
{
if(!vis[i]) prime[x++]=i;
for(j=0;j<x;j++)
{
if(i*prime[j]>n) break;
vis[i*prime[j]]=true;
tong[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
}
int main(){
init();
int t,i,j;
// for(i=999990;i<=1000000;i++)cout<
// return 0;
int b;
int n,r,l;
cin>>t;
while(t--){
scanf("%d",&n);
int p=tong[n];
if(p==0)p=n;
// if()
if(n==1)printf("1\n1\n");
else{
printf("%d\n",n-1);
for(i=p;i<=n;i+=p){
for(j=i-1;j>i-p;j--){
if(!(i==p&&j==i-1))printf(" ");
printf("%d",j);
}
if(i!=n)printf(" 1");
}
printf("\n");
}
}
}