暑假第一周周报
这周除了个人赛外,还进行了线段树、数状数组的练习。刚开始训练的时候,对线段数和数状数组是缺乏理解,感觉非常非常难,但随着做了越来越多的题。感觉现在是掌握了其中一部分。刚开始学线段树,其中的懒标记感觉不是太会,于是就网上找了一些资料,把那个懒标记的相关知识点给学了一下。个人感觉线段树主要就是分三部分。一个是对区间进行修改查询,一个是建立树,一个就是对现在懒标记的更新,其中个人感觉懒标记的更新要更加的困难。然后每一次写题的话,刚开始需要根据题解,然后逐渐逐渐去理解懒标记更新的意义与作用,到后面再做题的时候,就能掌握出其中的一些基本技巧。进行个人赛的时候,也根据有一些题去补充完善了自己的模板,发现写题解的人有一些思想就是特别特别好,相关算法的话。作为模板也非常合适,于是也进行了相关模板的补充。
以下是个人赛和线段树的一些补题情况
round 1
范围很小用暴力+贪心,左右枚举,先拿再放。尽量放小的所以需要排下序
include
include "map"
include "algorithm"
include "cmath"
include "vector"
include "set"
include "queue"
define int long long
using namespace std;
void solve(){
int n,k;
cin>>n>>k;
int s[n+5];
for(int i=1;i<=n;i++)
cin>>s[i];
int ans=-1e9;
for(int i=0;i<=min(n,k);i++){
for(int j=0;j<=min(n,k)-i;j++){
vector
int sd=0;
for(int q=1;q<=i;q++) {
vt.emplace_back(s[q]);
sd+=s[q];
}
for(int q=1;q<=j;q++) {
vt.emplace_back(s[n - q + 1]);
sd+=s[n-q+1];
}
sort(vt.begin(),vt.end());
for(int q=1;q<=k-i-j&&q<=vt.size();q++){
if(vt[q-1]<0)
sd-=vt[q-1];
else
break;
}
ans=max(ans,sd);
}
}
cout<<ans;
}
signed main()
{
int t=1;
//cin>>t;
while(t--){
solve();
}
}
这道题偷了个模板,我之前求组合数都是用的动态规划优化的,没想到题解用的优化的更猛。啥也别说了偷了
long long a(long long x,long long y){
long long ret=1;
while(y)
{
if(y&1)ret=retx%M;
x=xx%M;
y>>=1;
}
return ret;
}
long long b(long long x,long long y){//排列组合
long long sum=1,num=1;
for(int i=x;i>=x-y+1;i--)sum=sumi%M;
for(int i=1;i<=y;i++)num=numi%M;
return sum*a(num,M-2)%M;
}
打个比方求C(100,3)
cout<<b(100,3);
回到正题
考虑两个之间行距为 i 都可以随便选择一列,从 n−i 个行距为 i 的行组合中挑选一个,然后再选择剩余的k−2 个,那么可以推出方案数为i×(n−i)×m×m 再乘一个nm−2 和k−2 的排列组合就行了。
include<bits/stdc++.h>
define int long long
using namespace std;
const long long M=1000000007;
long long a(long long x,long long y){
long long ret=1;
while(y)
{
if(y&1)ret=retx%M;
x=xx%M;
y>>=1;
}
return ret;
}
long long b(long long x,long long y){//排列组合
long long sum=1,num=1;
for(int i=x;i>=x-y+1;i--)sum=sumi%M;
for(int i=1;i<=y;i++)num=numi%M;
return suma(num,M-2)%M;
}
signed main(){
int n,m,k;
cin>>n>>m>>k;
int ans=0;
for(int i=0;i<n;i++){
ans+=(i%M(n-i)%Mm%Mm%M)%M;
ans%=M;
}
for(int i=0;i<m;i++){
ans+=(i%M(m-i)%Mn%Mn%M)%M;
ans%=M;
}
ans=b(n*m-2,k);
ans%=M;
cout<<ans;
return 0;
}
round 2
刚开始写这道题因为看到范围比较小,所以打算直接把图形输出,后来逐渐尝试发现根本不行,到6的时候就要输出700多行了。看完题解后发现可以搜索。从8个方向去搜索,其中'.'赋为0,'#'为1.
include<bits/stdc++.h>
using namespace std;
int a[2000][2000];//0:黑,1:白
void dfs(int x,int y,int k){
if(k==0){
a[x][y]=0;
return ;
}
dfs(x,y,k-1);
dfs(x+pow(3,k-1),y,k-1);
dfs(x+pow(3,k-1)2,y,k-1);
dfs(x,y+pow(3,k-1),k-1);
dfs(x,y+pow(3,k-1)2,k-1);
dfs(x+pow(3,k-1),y+pow(3,k-1)2,k-1);
dfs(x+pow(3,k-1)2,y+pow(3,k-1),k-1);
dfs(x+pow(3,k-1)2,y+pow(3,k-1)2,k-1);
for(int i=x+pow(3,k-1);i<x+pow(3,k-1)2;i++){
for(int j=y+pow(3,k-1);j<y+pow(3,k-1)2;j++){
a[i][j]=1;
}
}
}
int main(){
int n;
cin>>n;
dfs(0,0,n);
for(int i=0;i<pow(3,n);i++){
for(int j=0;j<pow(3,n);j++){
if(a[i][j]) cout<<'.';
else cout<<'#';
}
cout<<endl;
}
}
因为节点 到最近黑色节点的最短距离已经是最小值,所以我们把最短距离小于给定距离的节点全部排除。然后判断对于一个节点,是否所有的距离等于给定距离的节点都被排除了。如果是,说明无解,输出 -1,结束程序。若不存在无解,则说明一定有解。把排除成为黑色节点的全部输出为白色节点,否则输出位黑色节点。
include <bits/stdc++.h>
define int long long
using namespace std;
pair<int,int> p1[2005],p2[2005];
map<int,int> ma;
map<int,set
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
p1[i]={a,b};
mp[a].insert(b);
mp[b].insert(a);
}
int k=1;
cin>>k;
for(int i=1;i<=k;i++){
int a,b;
cin>>a>>b;
p2[i]={a,b};
}
for(int i=1;i<=k;i++){
queue
map<int,int> mo;
num.push(p2[i].first);
cnt.push(0);
while(num.size()!=0){
int x=num.front(),y=cnt.front();
num.pop(),cnt.pop();
if(mo[x]||yp2[i].second) continue;
mo[x]=1,ma[x]=1;
for(auto x:mp[x]){
num.push(x);
cnt.push(y+1);
}
}
}
for(int i=1;i<=k;i++){
queue
int o=0;
bool f=false;
map<int,int> mo;
num.push(p2[i].first);
cnt.push(0);
while(num.size()!=0) {
int x=num.front(),y=cnt.front();
num.pop(),cnt.pop();
if(mo[x]) continue;
if(y
if(!ma[x]){
f=true;
break;
}
continue;
}
mo[x]=1,ma[x]=1;
for(auto x:mp[x]){
num.push(x);
cnt.push(y+1);
}
}
if(f==false){
cout<<"No\n";
return;
}
}
cout<<"Yes\n";
for(int i=1;i<=n;i++){
if(ma[i]){
cout<<'0';
}
else
cout<<'1';
}
}
signed main(){
int t=1;
//cin>>t;
while(t--)
solve();
}
Round 3
这道题是一道dp,但是这个题目有一点错误,它并没有说多组输入,愣是给我交了十几发才发现。这个就是二维dp,dp[i][j]其中i表示前i个数字中有j对的最小代价是什么。有两种转移方式。一个是加上最后的i,一个是不加最后的i也就是dp[i-1][j]。二者取最小。
include
include "algorithm"
include "cstring"
define inf 0x3f3f3f3f
using namespace std;
int dp[2005][2005];
int s[20005];
int main(){
int n,k;
while(cin>>n>>k){
memset(s,0,sizeof(s));
memset(dp,inf,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++){
cin>>s[i];
dp[i][0]=0;
}
sort(s+1,s+n+1);
for(int i=2;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j]=min(dp[i-1][j],dp[i-2][j-1]+((s[i]-s[i-1])*(s[i]-s[i-1])));
}
}
cout<<dp[n][k]<<'\n';
}
}
补E题时找到个集合求子集的板子,说实话还挺好用的。特在此收藏
输入n个数,把数据存放在数组a中。a是从0开始存放的。把子集存在vector
for(int i=1;i<(1<<n);i++){
int s=0;
vector
for(int j=0;j<n;j++){
if(i>>j&1){
temp.push_back(j);
s=(s+a[j])%200;
}
}
}
这个学到了鸽笼原理。因为是%200,所以余数一定小于200.所以求子集到8就够了。前256个子集中一定有两个子集余数相同。这时候就可以枚举了
include <bits/stdc++.h>
define int long long
using namespace std;
map<int,vector
void solve(){
int n;
cin>>n;
int s[n+5];
for(int i=0;i<n;i++)
cin>>s[i];
n=min(n,(int)8);
for(int i=1;i<pow(2,n);i++) {
vector<int> vt;
int num=0;
for(int j=0;j<n;j++){
if(i>>j&1){
vt.emplace_back(j+1);
num=(num+s[j])%200;
}
}
if(ma[num].size()==0){
ma[num]=vt;
}
else{
cout<<"Yes\n";
cout<<ma[num].size()<<' ';
for(auto x:ma[num]){
cout<<x<<' ';
}
cout<<'\n';
cout<<vt.size()<<' ';
for(auto x:vt){
cout<<x<<' ';
}
return;
}
}
cout<<"No\n";
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
solve();
return 0;
}
热身赛一
这道题正解是三分,但是我没用正解,用的是暴力+优化也能解决,因为分为四段,我是枚举中间点,然后在中间点的两端再去枚举,但是这样肯定会超时。优化的时候可以发现有很多部分是可以省略的。这部分就是减少代码时间的关键。 long long ans=1e18,p1,p2,p3,p4;
int l=1,r=3;//把l,r写在外面,不断循环就不用重头开始了
for(int i=2;i<=n-2;i++)
代码如下
include<bits/stdc++.h>
using namespace std;
long long fg[200005];
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&fg[i]);
fg[i]+=fg[i-1];
}
long long ans=1e18,p1,p2,p3,p4;
int l=1,r=3;
for(int i=2;i<=n-2;i++){
while(l<i&&abs((fg[i]-fg[l])-(fg[l]-fg[0]))>=abs((fg[i]-fg[l+1])-(fg[l+1]-fg[0])))
l++;
while(r<n&&abs((fg[n]-fg[r])-(fg[r]-fg[i]))>=abs((fg[n]-fg[r+1])-(fg[r+1]-fg[i])))
r++;
p1=fg[l]-fg[0];
p2=fg[i]-fg[l];
p3=fg[r]-fg[i];
p4=fg[n]-fg[r];
ans=min(max(max(max(p1,p2),p3),p4)-min(min(min(p1,p2),p3),p4),ans);
}
printf("%lld",ans);
return 0;
}
说实话这道题看半天没有看懂,后来才发现就是一个floyd的模板题。给你的10*10数组就是每一个点到其他点的距离。用floyd正好
include<bits/stdc++.h>
define INF 0x7fffffff/2 //int最大值
using namespace std;
int h,w;
int x,dis[15][15];
void floyd(){
for(int k=0;k<=9;k++){ //枚举每一个点
for(int i=0;i<=9;i++){
for(int j=0;j<=9;j++){
if(dis[i][k]!=INF and dis[k][j]!=INF) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); //松弛操作
}
}
}
}
int main(){
scanf("%d%d",&h,&w);
for(int i=0;i<=9;i++){
for(int j=0;j<=9;j++){ //i,j分别枚举0-9中的数字,也就是这条边连接的两个节点
scanf("%d",&dis[i][j]); //dis数组储存边权
}
}
floyd();
int ans=0;
for(int i=1;i<=h;i++){
for(int j=1;j<=w;j++){
scanf("%d",&x); //在输入时在线处理
if(abs(x)==1) continue; //如果这个点是1或-1,则不需要加在ans上
ans+=dis[x][1]; //累加x到1的最短路径
}
}
printf("%d",ans);
return 0;
}
热身赛二
这道题当时已经推出来公式了,但是如何枚举倒是没有想到,我们设这个商为 x,那么余数也为 x,而被除数 = 商 × 除数 + 余数,于是就有 n=mx+x=x(m+1),所以我们只需枚举 x即可求出 m.
include<bits/stdc++.h>
define int long long
using namespace std;
void solve(){
int n;
cin>>n;
int ans=0;
for(int i=1;i*(i+1)<n;i++){
if(n%i==0){
ans+=(n/i-1);
}
}
cout<<ans;
}
signed main()
{
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
这个是期望dp,当时不自量力去做,后来补题的时候发现好难啊。。。
第 i 面镜子会有两种情况。
第一种:镜子回答"漂亮",花一天走到下一面镜子。这种情况的贡献是 pi(dp(i+1)+1);
第二种:镜子回答"不漂亮",花一天走到第一面镜子。这种情况的贡献是:(1-pi)(dp1+1)
最终dpi=pi×(dp(i+1)+1)+(1−pi)×(dp1+1)=pi×dp(i+1)+(1−pi)×dp1+1;
但是这个还要dp1推一下,最终推导的公式因为符号的问题打不到博客上,这个还需要从后往前算
include<bits/stdc++.h>
using namespace std;
define int long long
const int mod=998244353;
int qwe(int a,int b,int p){
int ans=1;
while(b){
if(b&1)ans=ansa%p;
a=aa%p;
b/=2;
}
return ans;
}
int n;
int a[200010],ans,tmp;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)
cin>>a[i];
tmp=1;
for(int i=n;i>=1;i--){
tmp=tmp100%modqwe(a[i],mod-2,mod)%mod;
ans=(ans+tmp)%mod;
}
printf("%lld",ans);
return 0;
}
友谊赛
先求ab的前k项,再求abc的前k项。但是唯一的难点就是n^2的复杂度如何减到n。题解中用到了优先队列我觉得这个很值得参考
for (int i = 1; i <= y; i++) {
qt.push({a[1] + b[i], i});
pb[i] = 1;
}
for (int i = 1; i <= w && i <= x * y; i++) {
int u = qt.top().second;
int y = qt.top().first;
num[i] = y;
qt.pop();
qt.push(make_pair(a[++pb[u]] + b[u], u));
}
上述代码实现了ab的前w项,但是复杂度只有n*logn,结合了优先队列进行使用。
include<bits/stdc++.h>
using namespace std;
define int long long
int a[200005],b[200005],c[200005], num[200005],pb[200005],pc[200005];
priority_queue<pair<int,int>> qt;
bool compare(int a,int b){
return a>b;
}
signed main() {
int x, y, z, w;
cin >> x >> y >> z >> w;
for (int i = 1; i <= x; i++)
cin >> a[i];
for (int i = 1; i <= y; i++)
cin >> b[i];
for (int i = 1; i <= z; i++)
cin >> c[i];
sort(a + 1, a + x + 1, compare);
for (int i = 1; i <= y; i++) {
qt.push({a[1] + b[i], i});
pb[i] = 1;
}
for (int i = 1; i <= w && i <= x * y; i++) {
int u = qt.top().second;
int y = qt.top().first;
num[i] = y;
qt.pop();
qt.push(make_pair(a[++pb[u]] + b[u], u));
}
while(!qt.empty())
qt.pop();//清空q
for(int i=1;i<=z;i++){
qt.push(make_pair(c[i]+num[1],i));
pc[i]=1;
}
for(int i=1;i<=w;i++){
int bh=qt.top().second;
cout<<qt.top().first<<endl;
qt.pop();
qt.push(make_pair(num[++pc[bh]]+c[bh],bh));
}
return 0;
}
二分答案。这个矩阵的每一排都是递增的,所以二分ans,去计算有多少个数等于ans,有多少个数小于ans,如果小于ans的数不多于−1k−1个并且小于等于ans的数不少于k个,那么当前ans就是答案。
include<bits/stdc++.h>
using namespace std;
long long n,m,k;
bool qwe(long long x)
{
long long sum=0;
for(int i=1;i<=n;i++)
sum+=min(m,x/i);
if(sum>=k)
return true;
else
return false;
}
int main()
{
cin>>n>>m>>k;
long long pow=n*m;
long long l=1,r=pow;
while(l<r){
long long mid=(l+r)>>1;
if(qwe(mid))
r=mid;
else
l=mid+1;
}
cout<<l;
}
其中有一些线段树的题感觉很好,也进行了补题。
这道题刚开始做的时候因为不熟练,就相关线段树的知识点不是很明确,导致做起来很困难,然后研究了它懒标记的更新方法,以及区间存储的内容,算是第一次接触线段树的变换的题目。
include "bits/stdc++.h"
define int long long
const int maxn=1e4+10;
using namespace std;
int add[maxn<<2],mu[maxn<<2],sum[maxn<<2],num[maxn<<2];
void up(int i){//由子节点更新父节点
num[i]=num[i2]+num[i2+1];
sum[i]=sum[i2]+sum[i2+1];
}
void build(int rt,int l,int r)
{
if(lr)
{
scanf("%lld",&num[rt]);
sum[rt]=num[rt]num[rt];
return ;
}
int mid=(l+r)>>1;
build(rt2,l,mid);
build(rt2+1,mid+1,r);
up(rt);
}
void down(int i,int l,int r){//父节点更新子节点
int mid=(l+r)>>1;
if(add[i]){
sum[i2]+=(mid-l+1)add[i]add[i]+2num[i2]add[i];
sum[i2+1]+=(r-mid)add[i]add[i]+2num[i2+1]add[i];
add[i2]+=add[i];
add[i2+1]+=add[i];
num[i2]+=(mid-l+1)add[i];
num[i2+1]+=(r-mid)add[i];
add[i]=0;
}
if(mu[i]>1){
sum[i2]=mu[i]mu[i];
sum[i2+1]=mu[i]mu[i];
mu[i2]=mu[i];
mu[i2+1]=mu[i];
num[i2]=mu[i];
num[i2+1]=mu[i];
add[i2]=mu[i];
add[i2+1]*=mu[i];
mu[i]=1;
}
}
void p34(int i,int x,int y,int jk,int l,int r,int yu){
if(x<=l&&y>=r){
if(i4)
{
sum[yu]+=(r-l+1)jkjk+2num[yu]jk;
add[yu]+=jk;
num[yu]+=(r-l+1)jk;
}
else{
num[yu]=jk;
sum[yu]=jkjk;
add[yu]=jk;
mu[yu]=jk;
}
return;
}
down(yu,l,r);
int mid=(l+r)/2;
if(x<=mid)
p34(i,x,y,jk,l,mid,yu*2);
if(y>mid)
p34(i,x,y,jk,mid+1,r,yu*2+1);
up(yu);
}
int p12(int i,int x,int y,int l,int r,int yu){
if(x<=l&&y>=r){
if(i1)
return num[yu];
return sum[yu];
}
down(yu,l,r);
int mid=(l+r)/2;
int s=0;
if(x<=mid){
s+= p12(i,x,y,l,mid,yu2);
}
if(y>mid){
s+=p12(i,x,y,mid+1,r,yu2+1);
}
return s;
}
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
mu[i]=1;
build(1,1,n);
while(m--){
int a,b,c,d;
cin>>a>>b>>c;
if(a3||a==4){
cin>>d;
p34(a,b,c,d,1,n,1);
}
else{
cout<<p12(a,b,c,1,n,1)<<'\n';
}
}
}
signed main(){
int t=1;
while(t--){
solve();
}
}
本题最关键的就是对于一个区间如何维护懒标记以及两个加值:假设当前孩子结点的懒标记为a1,b1,现在父亲节点的懒标记为a2,b2要传递给孩子结点a2(a1x+b1)+b2=a2a1x+a2b1+b2,即现在的孩子结点乘的懒标记为a2a1,加的懒标记变为a2b1+b2。假设当前孩子结点的区间总和为t.sum,现在父亲结点的懒标记为a2,b2,要传递给孩子结点,孩子结点的区间总和变为(r-l+1)b2+t.sum*a2。中间其实要推一个个人感觉很难的公式,我也是研究了两个小时才慢慢理解公式的含义以及如何运用到懒标记的更新中。
include <bits/stdc++.h>
define int long long
using namespace std;
int n,m,p;
struct node{
int l,r,sum,num,add,mul;
} tree[400006];
int s[400005];
void up(int u){
tree[u].num=(tree[u2].num%p+tree[u2+1].num%p)%p;
tree[u].sum=((tree[u2].sum%p+tree[u2+1].sum%p)%p+(tree[u2].num)(tree[u2+1].num)%p)%p;
}
void ert(int u,int add,int mul){
int r=tree[u].r%p;
int l=tree[u].l%p;
int len=r-l+1;
tree[u].sum=(mulmul%ptree[u].sum%p+(len * (len - 1) / 2%p)add%padd%p+(r-l)tree[u].num%padd%pmul%p)%p;
tree[u].num=(tree[u].num%pmul%p+add(r-l+1)%p)%p;
tree[u].mul = (tree[u].mul * mul%p)%p;
tree[u].add = (mul * tree[u].add%p + add)%p;
}
void down(int u){
ert(u2,tree[u].add,tree[u].mul);
ert(u2+1,tree[u].add,tree[u].mul);
tree[u].add=0;
tree[u].mul=1;
}
void build(int u,int l,int r){
if(l==r){
tree[u]={l,r,0,s[l],0,1};
return;
}
int mid=(l+r)/2;
tree[u]={l,r,0,0,0,1};
build(u2,l,mid);
build(u2+1,mid+1,r);
up(u);
}
void update(int u,int x,int y,int add,int mul)
{
if(x<=tree[u].l&&y>=tree[u].r){
ert(u,add,mul);
return;
}
int mid=(tree[u].l+tree[u].r)/2;
down(u);
if(mid>=x){
update(u*2,x,y,add,mul);
}
if(y>mid){
update(u*2+1,x,y,add,mul);
}
up(u);
}
node print(int u,int x,int y){
if(tree[u].l>=x&&tree[u].r<=y)
return tree[u];
down(u);
int mid=(tree[u].l+tree[u].r)/2;
if(y<=mid)
return print(u2,x,y);
else
if(mid<x)
return print(u2+1,x,y);
else{
struct node p1,p2,ans;
p1= print(u2,x,y);
p2= print(u2+1,x,y);
ans.sum=((p1.sum+p2.sum)%p+(p1.num*p2.num)%p)%p;
ans.num=((p1.num)+(p2.num)%p)%p;
return ans;
}
}
void solve(){
memset(tree, 0, sizeof(tree));
cin>>n>>m>>p;
for(int i=1;i<=n;i++)
cin>>s[i];
build(1,1,n);
while(m--){
int a,b,c;
cin>>a>>b>>c;
if(a1||a2){
int x;
cin>>x;
if(a==1)
update(1,b,c,x,1);
else
update(1,b,c,0,x);
}
else{
node ans= print(1,b,c);
cout<<ans.sum%p<<'\n';
}
}
}
signed main(){
int t=1;
cin>>t;
while(t--)
solve();
}