我菜得很抱歉。。
A
B、pc玩游戏
wuwuwu这个题是个挺好想的二分。
可以看出,随着查询次数的变多,抽屉合法能放下的娃娃越来越少。
所以考虑二分答案,查询最早在什么位置合法放下的娃娃小于k。(要注意放娃娃不能相邻)
代码实现如下:
(我本来用set做的结果tle了(2000ms左右),用数组排序直接过了而且只跑了300ms左右)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;
const int maxn=2e5+7;
int a[maxn];
int tp[maxn];
int main(){
// FAST;
int n,k,L,m;
scanf("%d%d%d%d",&n,&k,&L,&m);
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
}
int temp1=n;
int temp2=0;
if(temp1>=L){
temp1-=L;
temp2++;
}
temp2+=temp1/(L+1);
if(temp2<k){
cout<<0<<endl;
return 0;
}
//二分
int l=1;
int r=m;
int temp=-1;
while(l<=r){
int mid=(l+r)>>1;
tp[0]=0;
for(int i=1;i<=mid;i++){
tp[i]=a[i];
}
tp[mid+1]=n+1;
sort(tp,tp+mid+2);
int putin=0;
for(int i=1;i<=mid+1;i++){
int nn=tp[i-1];
int nexx=tp[i];
int d=nexx-nn-1;
if(d>=L){
d-=L;
putin++;
putin+=d/(L+1);
}
}
if(putin<k){
temp=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
cout<<temp<<endl;
}
C pc买礼物
这是个dp,算方案数的dp(之前写过类似的,只不过那个是一维的而已。。),因为方案不同的判定条件为路径不同/携带礼物不同,所以dp数组记录到达的某点编号和当前礼物价值,(因为给出的单向边都是小节点指向大节点,直接按节点编号dp就行,不然还得拓扑序)。
思考一下,只需要记录当前总价值和当前所在节点就可以确定方案数。
代码实现:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;
//在图上dp
const int maxn=2e3+7;
const int mod=998244353;
vector<int> mp[maxn];
ll dp[maxn][maxn];
ll w[maxn];
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>w[i];
}
while(m--){
int a,b;
cin>>a>>b;
//a->b有一条有向边
//只要记录某个点从哪来
mp[b].pb(a);
}
dp[1][0]=1;
dp[1][w[1]]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<mp[i].size();j++){
int t=mp[i][j];
for(int x=0;x<=k;x++){
dp[i][x]=(dp[i][x]+dp[t][x])%mod;
if(x-w[i]>=0){
dp[i][x]=(dp[i][x]+dp[t][x-w[i]])%mod;
}
}
}
}
ll ans=0;
for(int i=0;i<=k;i++){
ans=(ans+dp[n][i])%mod;
}
cout<<ans<<endl;
}
D sb思维题,直接判断奇偶,就不放代码了。
E median
比赛的时候没看懂这“排列”的意思。。。。。原来v只会出现一次。。b瞎写了好久,绝了,一开始第一眼的思路就是对的。
因为要形成一个v是中位数的数组,那么数组中大于v和小于v的数量必须相等。
直接在数组中找到v的位置,看v之前任一段序列中有多少大于v的,多少小于v的,记录每个点到v形成的序列大于v和小于v的个数的差值,然后记录这个差值出现的次数。(别忘了前后各自有个位置是0,0)
v之后的序列一样处理。
然后前后组合就好了。
代码实现:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;
const int maxn=1e5+7;
int a[maxn];
map<int,ll> mp1,mp2;//分别记录前后缀差值信息
int main(){
int n,v;
scanf("%d%d",&n,&v);
int i0=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]==v){
i0=i;
}
}
int cntb=0;
int cnts=0;
mp1[0]++;
mp2[0]++;
for(int i=i0-1;i>=1;i--){
if(a[i]<v){
cnts++;
}
else{
cntb++;
}
mp1[cnts-cntb]++;
}
cntb=0;
cnts=0;
for(int i=i0+1;i<=n;i++){
if(a[i]<v){
cnts++;
}
else{
cntb++;
}
mp2[cnts-cntb]++;
}
ll ans=0;
for(int i=0;i<=n;i++){
//cout<
ans+=mp1[-i]*mp2[i];
if(i!=0)
ans+=mp1[i]*mp2[-i];
}
printf("%lld\n",ans);
}
F 重建网络
这个题挺有意思的,开始和学长讨论还以为这个是个假题,后来看了题解的解释,才发现这个题的妙处。
思路就是对给出的无向图跑一次最大生成树,如果用到的边有小于k的,那么答案sum(小于k的且用到的边-k)。
如果用到的边都大于k,答案就是所有的边与k最小的差值。(为什么呢?)
通过思考可以发现,当前已经形成树了,如果与k差值最小的那个边不在树上,可以直接替换上去。(删去换上去的边两节点(a,b)任意点与集合相连的那个边即可)
(非主流最大生成树写法。。。堆优化prim,边权全部取负数)
代码实现:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define pb push_back
#define endl '\n'
using namespace std;
const int maxn=1e5+7;
const int maxm=2e5+7;
struct edge{
int to;
ll w;
};
vector<edge> mp[maxn];
typedef pair<ll,int> P;//first为距离,second为编号
bool vis[maxn];
ll dis[maxn];
int n,m;
ll k;
ll cntedge[maxn];
ll ed[maxm];
int cnt=0;
void prim(){
//最大生成树
priority_queue<P,vector<P>,greater<P> >q;
q.push({
0,1});//随便让一个点入队;
while(q.size()){
P t=q.top();
q.pop();
ll w0=t.first;
ll v0=t.second;
//cout<<"w0="<
if(vis[v0]) continue;
vis[v0]=1;
if(w0!=0)
cntedge[++cnt]=-w0;
for(int i=0;i<mp[v0].size();i++){
edge e=mp[v0][i];
q.push({
e.w,e.to});
}
}
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
int a,b;
ll c;
cin>>a>>b>>c;
mp[a].pb({
b,-c});//建图用负边权
mp[b].pb({
a,-c});
ed[i]=c;
}
prim();
ll ans=0;
int flag=0;
ll minn=1e15;
for(int i=1;i<=cnt;i++){
//cout<<"cntedge="<
if(cntedge[i]<k){
flag=1;
ans+=abs(k-cntedge[i]);
//cout<<"abs="<
}
}
//cout<<"flag="<
if(flag==1){
cout<<ans<<endl;
}
else{
for(int i=1;i<=m;i++){
minn=min(minn,abs(k-ed[i]));
}
cout<<minn<<endl;
}
}
G
H pc要出题
对不起对不起对不起我以为1e7数组会爆硬是用了map查询,然后把我tle到傻,又被容器坑了一把。
问了大佬同学,他说如果map用下标查询原本不存在的节点,会现场建一个节点,时间成本较大,所以建议用find查询map。
要两个数的和能除尽k,那么就是两个数取模和等于k,就是个sb题。
(这里分享两种写法)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FAST ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
#define ll long long
#define endl '\n'
using namespace std;
ll cnt[10000007];
int a[1000007];
bool vis[100000007];
int main(){
int n,k;
scanf("%d%d",&n,&k);
ll ans=0;
for(int i=1;i<=n;i++){
ll temp;
scanf("%lld",&temp);
temp%=k;
//a[i]=k;
cnt[temp]++;
}
for(int i=0;i<k;i++){
if(vis[i]==1) continue;
if(i==0|k-i==i){
//记得特判
ans+=cnt[i]*(cnt[i]-1)/2;
vis[i]=1;
}
else{
ans+=cnt[i]*cnt[k-i];
vis[i]=1;
vis[k-i]=i;
}
}
printf("%lld\n",ans);
}
int cnt[10000007];
int a[1000007];
int main(){
int n,k;
scanf("%d%d",&n,&k);
ll temp;
ll ans=0;
while(n--){
scanf("%lld",&temp);
temp%=k;
if(temp==0){
ans+=cnt[0];
}
else{
ans+=cnt[k-temp];
}
cnt[temp]++;
}
printf("%lld\n",ans);
return 0;
}
(还有一个莫队一个dp没写,写完马上补上题解)