A~E题
A题
先找到>=l的最小x的倍数再判断是否<=r
或者先找到<=r的最大x的倍数再判断是否>=l都可以
void solved()
{
int l,r,x;
cin>>l>>r>>x;
int mi=(l+x-1)/x*x;//>=l的最小x的倍数
cout<<(mi<=r?mi:-1);
// int ma=r/x*x;//<=r的最大x的倍数
// cout<<(ma>=l?ma:-1);
}
B题
满足条件的最短长度区间[x,n*x]往左右两边扩
左边右边均最多扩x-1长度
注意需要开longlong
void solved()
{
int n,k,x;
cin>>n>>k>>x;
int len=(n-1)*x+1;//满足条件的最短长度区间[x,n*x]
if(k>=len && k<=len+2*(x-1))
{
if(k<=len+x-1)cout<<x-(k-len)<<' '<<n*x<<'\n';//仅扩展左边
else cout<<x-(k-len-(x-1))<<' '<<n*x+x-1<<'\n';//左右都扩展
}
else cout<<-1<<'\n';
}
C题:构造
无解情况:因为是n个数的排列,所以前n个数一定是排列,故最后一个字符必须为'1',否则就无解
考虑其他情况:一种构造方法是先构造数组a=1,2,3,...n的排列,然后对于每个'1'左边如果存在0子串,则与前面0子串最左边的0交换a值,其他情况a值不变即可
void solved()
{
int n;cin>>n;
string s;cin>>s;s=" "+s;
if(s[n]=='0')//答案不存在
{
cout<<-1;
return ;
}
vector<int> a(n+1);
for(int i=1;i<=n;i++)a[i]=i;//初值
int pre0=0;//当前位置左边0子串的最左边0的下标(pre0=0表示左边不存在0子串)
for(int i=1;i<=n;i++)
{
if(pre0==0 && s[i]=='0')pre0=i;
if(s[i]=='1' && pre0)swap(a[i],a[pre0]),pre0=0;
}
for(int i=1;i<=n;i++)cout<<a[i]<<' ';
}
D题:双指针(滑动窗口)
- 设当前区间为[l,r],区间中的01子序列为x个
- 考虑相邻区间[l,r+1],区间中01子序列一定>=x个
- 考虑相邻区间[l+1,r],区间中01子序列一定<=x个
根据相邻区间答案具有单调性我们使用滑动窗口来实现
当前区间01子序列数x<k时就向右扩展右端点使x变大,看是否能使x==k,x>k就退出
当前区间01子序列数x>k时就向右扩展左端点使x变小,看是否能使x==k,x<k就退出
建议这种滑动窗口的题用队列实现,而不用原始的双指针实现,这样会简单很多不容易出错,其本质都是一样的
void solved()
{
int n,k;cin>>n>>k;
string s;cin>>s;s=" "+s;
queue<int> q;
int cnt0=0,cnt1=0,x=0;//x为当前01子序列数
for(int i=1;i<=n;i++)
{
q.push(i);//加入队列
if(s[i]=='1')x+=cnt0,cnt1++;
else cnt0++;
while(q.size() && x>k)
{
int x=q.front();q.pop();
if(s[x]=='0')sum-=cnt1,cnt0--;
else cnt1--;
}
if(sum==k)
{
cout<<q.front()<<' '<<q.back();//输出左端点和右端点
return ;
}
}
cout<<-1;
}
E题:01背包
赛时没注意,以为不能有重边,百分之八十多一直没调出来QAQ
- 设度数之和为sum
- sum==2*边数:所以度数之和不能为奇数
- sum为偶数情况下,二分图必须满足左边点度数和==右边点度数和==sum/2,可以理解为sum/2条边来连接两边
所以问题就变成了n个点中能否挑一些出来度数和为sum/2(剩下的点度数也为sum/2)
思路过程:跑01背包-->跟踪一种解-->暴力连边**
void solved()
{
int n;cin>>n;
vector<int> d(n+1);
int sum=0;
for(int i=1;i<=n;i++)cin>>d[i],sum+=d[i];
if(sum&1)//特判:度数之和不能为奇数
{
cout<<-1;
return ;
}
//定义状态f[i][j]:前i个中选,度数和恰好为j的最多个数 (最少个数也行)
//ff数组用来跟踪解,ff[i][j]:当前的f[i][j]是否选了第i个数(0表示没选,1表示选了)
vector<vector<int>> f(n+1,vector<int> (sum/2+1,-1e9));
vector<vector<int>> ff(n+1,vector<int> (sum/2+1,0));
for(int i=0;i<=n;i++)f[i][0]=0;//注意初值
for(int i=1;i<=n;i++)//跑01背包
for(int j=1;j<=sum/2;j++)
{
if(j>=d[i] && f[i-1][j]<=f[i-1][j-d[i]]+1)f[i][j]=f[i-1][j-d[i]]+1,ff[i][j]=1;//选
else f[i][j]=f[i-1][j];//不选
}
int cnt=0;
vector<int> l,r;
if(f[n][sum/2]<=0)//无解:找不到一种组合使得度数和为sum/2
{
cout<<-1;
return ;
}
else//有解
{
int now=sum/2;
for(int i=n;i>=1;i--)//跟踪解
{
if(ff[i][now])l.push_back(i),now-=d[i],cnt+=d[i];//选了放左边
else r.push_back(i);//没选放右边
}
}
cout<<cnt<<'\n';
for(int lx:l)//已知左边点集l和右边点集r情况下直接暴力连边即可
for(int rx:r)
{
int z=min(d[lx],d[rx]);
d[lx]-=z,d[rx]-=z;
while(z--)cout<<lx<<' '<<rx<<'\n';
}
}