D队友补了不太想写了。之后可能会把C(ntt)和L(计算几何)补了
A题
背包,贪心,数学
题意:给定n个物品,每个物品有一个w值和p值,要你从中选出m个并且对这个m个物品排序使得如下公式的值最大。
对于这个公式,我们显然能看出m物品的先后顺序是对这个公式的答案有影响的。
我们考虑从中,前,后,三个方向插入第m个物品看对原m-1个物品的价值影响。
发现中,后方插入后公式推导困难,而前方插入公式推导简单
前方插入物品为(w,p)那么新的价值就是w+p原价值
显然可以按照插入价值拍个序,
对于a插入的价值大于b插入价值的条件就是:Wa+Pa(Wb+Pbpre)>Wb+Pb(Wa+Papre)
化简后有
Wa+PaWb>Wb+Pb*Wa
排序完后从前完后背包并且贪心的先选插入价值大的即可。
然而一维01背包是从后往前选择物品的,这样我们就让排序规则为Wa+PaWb
#include
#include
#include
using namespace std;
struct node
{
double p,w;
};
const int N =1e5+100;
int n,m;
node a[N];
double dp[N];
bool cmp(const node &a,const node &b)
{
return a.w+a.p*b.w<b.w+b.p*a.w;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i].w;
for(int i=1;i<=n;i++)
{
cin>>a[i].p;
a[i].p/=10000.0;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
for(int j=min(i,m);j>=1;j--)
dp[j]=max(dp[j],a[i].w+a[i].p*dp[j-1]);
cout<<fixed<<setprecision(12)<<dp[m]<<endl;
return 0;
}
H
构造,数学
题意:给定一个整数n,则有长度为n砖1块,n-1的2块,一直到长度为1的砖n块,这些砖头宽度均为1
要求你用上所有砖垒一堵矩形的墙,输出这堵墙周长最小的方案数和每一块砖的左上角以及右下角。
思路:首先是周长,我们易知面积是一定的,又知area=n*m。所以可以枚举最相近的n和m值
然后让长度最小的情况下,一层一层的垒砖就可以了
#include
using namespace std;
int l,h,num[200+100];
int main()
{
int t;
for(cin>>t;t;t--)
{
int n,area=0;
cin>>n;
for(int i=1;i<=n;i++)
{
area+=(n-i+1)*i;
num[i]=n-i+1;
}
for(int i=1;i*i<=area;i++)
{
if(area%i==0)
{
l=area/i;
h=i;
}
}
cout<<(l+h)*2<<endl;
for(int i=0;i<h;i++)
{
int temp=0;
while(temp<l)
{
int j=min(n,l-temp);
while(!num[j])
{
j--;
}
cout<<temp<<" "<<i<<" "<<temp+j<<" "<<i+1<<endl;
temp+=j;
num[j]--;
}
}
}
return 0;
}
K
SB
题意:变量a要顺序的遍历1到n,当且仅当a取模n同余于i(i代表1-n的第i个数)时算i被遍历了,并且可以继续遍历下一项。a的初始值为0,遍历前可以进行0-x次操作。求遍历整个数组需要的最小操作次数
操作定义:a=a*10+x(x是从0到9的任意一个数)
思路:首相易知n为1情况下答案是0.
遍历第i项时,a的数值取模于n肯定是i-1,也就是有效部分就是i-1,也就是a就可以当做i-1考虑。
a进行一次操作,能和a取模于n同余的数就多了。
第一次操作
[a10%n,a10%n+10)(这个最大值是取不到的)
第二次操作
[a1010%n,a1010%n+10*10)
以此类推
所以只要让i%n或者i%n+n在这个范围里就好了
#include
using namespace std;
int n,m,ans;
int calc(int x,int y)
{
int cnt=1,len=10;
x=10*x%n;
while(1)
{
int l=x,r=x+len;
if((y>=l&&y<r)||(y+n>=l&&y+n<r))
return cnt;
++cnt;
len*=10;
x*=10;
x%=n;
}
}
int main()
{
cin>>n;
if(n==1)
{
cout<<0<<endl;
return 0;
}
for(int i=1;i<=n;++i)
{
ans+=calc(i-1,i%n);
}
cout<<ans<<endl;
return 0;
}
N
思维题
题意:给定n个数,这n个数可以选择任意两个数然后做and和or运算产生两个值,无限次操作之后数组趋于稳定,问这个稳定数组的方差。
很容易想到二进制状态的位运算,对齐两个数,只有这两个数的二进制位不同时才会交换对应二进制位的0和1.相同时没有变化。所以最终的稳定数组应当是在二进制视角下的两极分化。
比如:010 110 001最终会变成111 010 000.
然后就是求方差的过程。
发现根据数据范围最大到1e25.爆longlong当时int128可以接受。
另一种方法是优化公式。
#include
using namespace std;
#define ll __int128
const ll N = 1e5+100;
ll a[N];
ll bit[20];
ll b[N];
__int128 read()
{
__int128 f=1,w=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
w=w*10+ch-'0';
ch=getchar();
}
return f*w;
}
void print(__int128 x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)print(x/10);
putchar(x%10+'0');
}
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
int main()
{
ll n;
n=read();
for(ll i=1;i<=n;i++)
{
a[i]=read();
for(ll j=0;j<=15;j++)
{
ll temp=(a[i]>>j);
if(temp&1)
{
bit[j]++;
}
}
}
for(ll i=1;i<=n;i++)
{
for(ll j=15;j>=0;j--)
{
if(bit[j])
{
bit[j]--;
b[i]+=1<<j;
}
}
}
ll sum=0;
ll sum1=0;
for(ll i=1;i<=n;i++)
{
sum+=b[i];
}
for(ll i=1;i<=n;i++)
{
sum1+=(n*b[i]-sum)*(n*b[i]-sum);
}
ll mm=n*n*n;
print(sum1/gcd(sum1,mm));
printf("/");
print(mm/gcd(sum1,mm));
return 0;
}
再做两个SA的题今天就下班了。