A - City:
直接推公式,一开始以为要用大数,其实不用。
组合数。
#include
using namespace std;
typedef long long ll;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
ll ans=0,a=0,b=0;
for(int i=2;i<=n;i+=2)
a+=(n-i+1);
for(int j=2;j<=m;j+=2)
b+=(m-j+1);//cout<
ans=a*(m+1)+b*(n+1);
for(int i=2;i<=n;i++)
ans+=b*(i/2)*2;
printf("%lld\n",ans);
}
return 0;
}
M - Value:
思路:二进制状压暴力
从2开始,每当找到一个最小的底数,就把他所有允许范围内的所有指数全部枚举完,二进制的形式描述各种指数对应的数是否存在于集合A中,然后枚举各种情况,求出最大值,加入集合A中。
这种枚举方法比较常用,记住!
#include
using namespace std;
typedef long long ll;
const int N=1e5+5;
int a[N],b[N];
int num[50];
bool vis[N];
ll ans;
void solve(int m)
{
ll maxn=0;
for(int i=1;i<(1<<m);i++)//二进制枚举
{
ll sum=0;
for(int j=0;j<m;j++)
{
if(i&(1<<j))
{
sum+=a[num[j]];
int p=j+1;
for(int k=p+p;k<=m;k+=p)//注意理清楚倍数关系
if(i&(1<<(k-1)))
sum-=b[num[k-1]];
}
}
maxn=max(maxn,sum);
}
ans+=maxn;//cout<<"------"<
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
ans=a[1];
memset(vis,0,sizeof(vis));
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
int x=0;
for(ll j=i;j<=n;j*=i)//int: re
{
num[x++]=j;
vis[j]=1;
}
solve(x);
}
}
printf("%lld\n",ans);
return 0;
}
H - King:
随机化法
完全没有想过用这种方法解题。QWQ
因为我们要找是否存在长度大于 n/2 ,所以我们随机选择两个数字,这两个数字出现在答案序列中的概率至少为 1/4 ,如果我们选择多次,那么答案的正确性为:1 - (3/4)^x,当x比较大的时候是可以保证正确率的。
但是现在选择的两个数字可能差距了几个公比,怎么办呢?我随机两个相邻的数字即可。什么意思呢?就是我们令当前随机出来的两个数字是答案中相邻的数字,可以证明最小的距离最大值是不会超过2的,所以直接判断x,x+1和x,x+2即可,都判断一次。
一直WA在了test 18,用别人一样思路的代码可以过,经过长时间的对比(><)发现原来是随机数的问题。我一开始用的是rand()产生的随机数,发现别人用的都是用
mt19937来输出随机数。
它是C++11中新加入的新特性,是一种随机数算法,用法与rand()函数类似,但是具有速度快,周期长的特点(它的名字便来自周期长度:2^19937-1),说的直白一点,我们都知道rand()在windows下生成的数据范围为0-32767(数太小了),但是这个函数的随机范围大概在(−maxint,+maxint)(maxint为int类型最大值)。
用法:
#include
using namespace std;
int main() {
mt19937 mt_rand(time(0));
cout << mt_rand() << endl;
return 0;
}
AC代码:(本地编译过不了,要改为C++11标准)
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=2e5+5;
ll b[N],inv[N];
ll qpow(ll a,ll b,ll mod)
{
ll res=1;
while(b)
{
if(b&1)
res=(res*a)%mod;
a=a*a%mod;
b>>=1;
}
return res%mod;
}
void init(ll p,int n)
{
for(int i=1;i<=n;i++)
inv[i]=qpow(b[i],p-2,p);
}
int solve(int x,int y,ll p,int n)
{
ll q=b[y]*inv[x]%p;
int last=x,ans=2;
for(int i=x-1;i>=1;i--)
{
if(b[last]*inv[i]%p==q)
{
ans++;
last=i;
}
}
int pre=y;
for(int i=y+1;i<=n;i++)
{
if(b[i]*inv[pre]%p==q)
{
ans++;
pre=i;
}
}//cout<<"ans="<
return ans;
}
int main()
{
int t;
scanf("%d",&t);
mt19937 mt_rand(time(0));
while(t--)
{
int n,ans=0,tt=-1;
ll p;
scanf("%d%lld",&n,&p);
for(int i=1;i<=n;i++)
scanf("%lld",&b[i]);
init(p,n);
for(int i=1;i<=150;i++)//足够的迭代次数
{
int x=mt_rand()%(n-1)+1;
for(int i=x+1;i<=n&&i<=x+2;i++)
ans=max(ans,solve(x,i,p,n));
}
printf("%d\n",ans>=(n+1)/2?ans:tt);
}
return 0;
}
E - Flow:
1.总的边权值得和不变,所以最终的最大流量值是固定的,而初始的流量也已知,所以可以求出增量。
2.整个过程就相当于平均化边权,因此只管把小于平均值得边权加上去就行。
3.要使流量+1,可能需要修改多条边。
4.注意两个点的情况,和所有边权都相等的情况。
#include
using namespace std;
const int N=1e5+5;//
typedef long long ll;
typedef pair<int,int> P;
vector<P> pic[N],edge;
priority_queue<int,vector<int>,greater<int> >que;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
int x,y,z;
ll sum=0,res=0;//所有的边的总长度
for(int i=0;i<=n;i++)
pic[i].clear();
while(!que.empty())
que.pop();
edge.clear();
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
pic[x].push_back(make_pair(y,z));
sum+=z;
}
int len=0,p=1;//len:路径长度
while(p!=n)
{
len++;
p=pic[p][0].first;
}
if(len==1)
{
printf("0\n");
continue;
}
sum/=len;//最终的最大流量(平均)
for(int i=0;i<pic[1].size();i++)//遍历每一条路径
{
P now=pic[1][i];
p=now.first;
que.push(now.second);
while(p!=n)//把每条路径的所有边入队排序
{
now=pic[p][0];
que.push(now.second);
p=now.first;
}
int tp=que.top();
int cnt=1;
que.pop();
res+=tp;//初始时刻的流量(每条路径的最短边的容量和)
while(!que.empty())
{
edge.push_back(make_pair(cnt++,que.top()-tp));//次序,差值
tp=que.top();
que.pop();
}
}
sum-=res;//需要的增量
sort(edge.begin(),edge.end());//先按在路径中的次序然后按差值大小进行排序
p=0;
ll ans=0;
while(sum>=edge[p].second&&p<edge.size())//注意:如果所有边的权值都相等,那么那么p
{//找了好久才发现的错误
ans+=(1LL)*edge[p].first*edge[p].second;//要修改多条边
sum-=edge[p].second;
p++;
if(sum==0)
break;
}
ans+=(1LL)*edge[p].first*sum;
printf("%lld\n",ans);
}
return 0;
}
C - Dirichlet k k k-th root:
狄利克雷卷积性质:
快速幂&卷积:
如果 f f f在模 m o d mod mod下进行 k k k次卷积得到 g g g,那么 g g g进行 i n v ( k ) inv(k) inv(k)次卷积后就会得到 f f f。
(目前不会证明)
这种方法待学:推荐题解
#include
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int p=998244353;
ll g[N],f[N],t[N];
void read(ll &x)
{
char cc;
int f=1;
cc=getchar();
while(cc<'0'||cc>'9')
{
if(cc=='-')
f=-1;
cc=getchar();
}
while(cc>='0'&&cc<='9')
{
x=10*x+cc-'0';
cc=getchar();
}
x=x*f;
}
ll power(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)
res=res*a%p;
a=a*a%p;
b>>=1;
}
return res%p;
}
void mul(ll a[],ll b[],int n)
{
memset(t,0,sizeof(t));
/*for(int i=1;i<=n;i++):Time limit exceeded on test 26 牛客:599ms
{
for(int j=i;j<=n;j+=i)
t[j]=(t[j]+a[i]*b[j/i]%p)%p;
}*/
/*for(int i=1;i<=n;i++):936ms
{
for(int j=1;j*i<=n;j++)
t[i*j]=(t[i*j]+a[i]*b[j]%p)%p;
}*/
for (int i=1;i*i<=n;i++) {//686ms
t[i*i]=(t[i*i]+a[i]*b[i]%p)%p;
for (int j=i+1;i*j<=n;j++) {
t[i*j]=(t[i*j]+a[i]*b[j]%p+a[j]*b[i]%p)%p;
}
}
for(int i=1;i<=n;i++)
a[i]=t[i];
}
void qpow(ll a[],ll b,int n)
{
f[1]=1;
while(b)
{
if(b&1)
mul(f,a,n);
mul(a,a,n);
b>>=1;
}
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
read(g[i]);
f[i]=0;
}
ll inv=power((ll)k,(ll)p-2);//k的逆元
qpow(g,inv,n);
for(int i=1;i<=n;i++)
printf("%lld%c",f[i],i==n?'\n':' ');
return 0;
}
水平有限目前只能先补到这里,后面的题待补。。。
推荐题解1
推荐题解2