牛客18454I 题目链接 :点击这里传送
题意:给出三种类型的拼图块的数量
角(与外界有两块接触)边(与外界有一块接触) 肚皮(与外界没有接触)
求这样有没有可能组成一幅完整的拼图,如果可能输出这个拼图的长和宽
分析:题目已保证是个矩形且不会有一字长龙的情况出现(这样角与外界接触的面就是3了)
那么在确保角的个数为4的情况下遍历长和宽使得 长×宽=肚皮的个数 即可
需注意的是输出时 长>宽 且 对于肚皮个数为0的情况的处理(遍历时从0开始)或特判
#include<bits/stdc++.h>
using namespace std;
int n,m,k;//角,边,肚皮
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
if(n!=4)
{
cout<<"impossible";
return 0;
}
if(m%2==1)
{
cout<<"impossible";
return 0;
}
//最终的i和j都要加2,因为角美算进去
for(int i=0;i<=m;i++)//枚举一条边
{
int j=m-2*i;
if(j<0) break;
j=j/2;
if(i*j==k)
{
if(i>j)
cout<<i+2<<" "<<j+2;
else if(i<=j)
{
cout<<j+2<<" "<<i+2;
}
return 0;
}
}
cout<<"impossible";
return 0;
}
牛客18454C 题目链接:点击这里传送
题意:给出一张ACM的罚时表(罚时为最新一道题的过题时间),输入n个人,k个题目,问有没有可能得到一张唯一的排名表
分析:首先得保证有人AK(如果不是这样的话可以整体加一,就不可能唯一)。设冠军为k,顺序遍历下来,如果罚时比前面的人高就继承前一个人的过题数量,否则就是前一个过题数量-1。到最后有三种情况可以判断排名不唯一
- 过题数为0的人罚时不为0
- 罚时为0的人过题数不为0
- 罚时不为0的最后一个人过题数不为1
#include<bits/stdc++.h>
using namespace std;
int n,k;
#define MAXN 10005
int a[MAXN];
int b[MAXN];//罚时为0过题数不为0,过题数为0罚时不为0,过题数不为1罚时不为0
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
if(a[1]==0)
{
for(int i=1;i<=n;i++) cout<<0<<endl;
return 0;
}
int rank=k;
b[1]=k;
for(int i=2;i<=n;i++)
{
if(a[i]>=a[i-1]) b[i]=b[i-1];
else b[i]=b[i-1]-1;
}
if(b[n]==0&&a[n]!=0||b[n]!=0&&a[n]==0||b[n]!=1&&a[n]!=0)
{
cout<<"ambiguous"<<endl;
return 0;
}
for(int i=1;i<=n;i++) cout<<b[i]<<endl;
return 0;
}
牛客18454D 题目链接:点击这里传送
题意:给出一张三维平面的图,在此题中建筑物可以到达不比其高的附近建筑物(附近的附近也算)的任意一层。问最少需要建造几个电梯能遍历完图中所有建筑物的所有楼层
一楼不用电梯
思路:使用结构体存储一个建筑物的坐标和高度,输入完后根据高度进行降序排序。从最高的建筑物开始BFS,在满足 不越界 高度不比初始建筑物低 在附近 未遍历过的 的条件下将其push入队。这样一共进行了几趟BFS就是最少建造电梯的数量
#include<bits/stdc++.h>
using namespace std;
#define MAXN 505
int cnt;
int ans;
int mp[MAXN][MAXN];
int visit[MAXN][MAXN];
struct node {
int x; int y; int h;
}a[MAXN*MAXN];
bool cmp(node a, node b) { return a.h > b.h; }
int n, m;
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };
bool ok(int x, int y)
{
if (x >= 1 && x <= n && y >= 1 && y <= m) return 1;
return 0;
}
void bfs(int x,int y)
{
queue <node> q;
visit[x][y] = 1;
q.push(node{ x,y,mp[x][y] });
while (!q.empty())
{
node b = q.front();
q.pop();
int x = b.x; int y = b.y; int h = b.h;
for (int i = 0; i < 4; i++)
{
int xx = b.x + dx[i]; int yy = b.y + dy[i];
if (ok(xx, yy) && mp[x][y] >= mp[xx][yy] && visit[xx][yy] == 0)
{
visit[xx][yy] = 1;
q.push(node{ xx,yy,mp[xx][yy] });
}
}
}
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cnt++;
int h;
cin >> h;
mp[i][j] = h;
a[cnt].x = i; a[cnt].y = j; a[cnt].h = h;
}
}
sort(a + 1, a + cnt + 1, cmp);
for (int i = 1; i <= cnt; i++)
{
int x = a[i].x; int y = a[i].y; int h = a[i].h;
if (h <= 1) continue;
if (visit[x][y] == 0)
{
ans++;
bfs(x,y);
}
}
cout << ans;
return 0;
}
牛客18454F 题目链接:点击这里传送
题意:一共有n个点m条边。n个点中起码得有一个发电站为剩余城市供电。现给出在这n个点修建发电站的代价,(1,n)的电路代价,(i,i+1)的电路代价 i∈(1,n-1)求最小的代价和。
思路:可以引入0这个点。如果在某个点修建发电站,可以将其修改为将这个点和0连一条边,代价为修建发电站的代价。这样题目就转变成了维护一个n+1个点,n+m条边的最小生成树。
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
struct node{
int from,to,val;
}edge[2*MAXN];
int cnt,n,m;
int f[MAXN];
bool cmp(node a,node b){return a.val<b.val;}
int findx(int x)
{
if(x==f[x]) return x;
return f[x]=findx(f[x]);
}
void merge(int x,int y)
{
int fx=findx(x);
int fy=findx(y);
if(fx!=fy) f[fy]=fx;
}
long long mst()
{
long long ans=0;
int my_count =0;
for(int i=1;i<=n;i++) f[i]=i;
sort(edge+1,edge+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
int a=edge[i].from;
int b=edge[i].to;
if(findx(a)!=findx(b))
{
f[findx(b)]=findx(a);
my_count++;
ans+=edge[i].val;
}
if(my_count==n)
{
return ans;
}
}
return 99999999;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
edge[++cnt].from=0;
cin>>edge[cnt].to>>edge[cnt].val;
}
for(int i=1;i<n;i++)
{
edge[++cnt].from=i;edge[cnt].to=i+1;
cin>>edge[cnt].val;
}
edge[++cnt].from=1;edge[cnt].to=n;cin>>edge[cnt].val;
cout<<mst();
return 0;
}
牛客18454H 题目链接:点击这里传送
题意:给出一个排列,每次可以并只能从中选取个元素并根据这些元素的相对位置进行排序。要求你在最多三次内完成整体的排序(不用最小化)。输出每次选取元素在排列中的位置
n保证能被4整除
思路:第一次保证前的位置完成排序,第二次保证(,]的位置完成排序,第三次将剩下的一半元素排序。
实操的话其实很麻烦,每一次都应该把区间内本应出现的元素和实际出现的元素归为一组进行排序。如果这些元素之间出现重复情况的话还必须加入别的元素使得个数满足。完事了之后还得维护原数组因为前面的结果是会对后续元素的选取造成影响的。
#include<bits/stdc++.h>
using namespace std;
//每次选出n/2的长度使其完成排序,三次操作后使其总体完成排序
//第一次 选出前n/4和最小的n/4,使其完成排序
//第二次 继续n/4
//第三次,剩余的n/2
int n;
#define MAXN 100005
int locate[MAXN];
int a[MAXN];//一个数字在数组中的位置
int visit[MAXN];
int now;
void check()
{
cout<<"这是原数组"<<endl;
for(int i=1;i<=n;i++) cout<<a[i]<<" ";
cout<<endl<<"这是位置数组"<<endl;
for(int i=1;i<=n;i++) cout<<locate[i]<<" ";
cout<<endl;
}
void first_insert()
{
int first_cnt=0;
for(int i=1;i<=n/4;i++)
{
visit[i]=1;visit[locate[i]]=1;
if(locate[i]>n/4) first_cnt+=2;
else first_cnt+=1;
}
while(first_cnt<n/2)
{
for(int i=1;i<=n;i++)
{
if(visit[i]==0)
{
visit[i]=1;
first_cnt++;
}
if(first_cnt==n/2) break;
}
}
}
void second_insert()
{
int second_cnt=0;
for(int i=n/4+1;i<=n/2;i++)
{
visit[i]=1;visit[locate[i]]=1;
if(locate[i]>n/2) second_cnt+=2;
else second_cnt++;
}
while(second_cnt<n/2)
{
for(int i=n/4+1;i<=n;i++)
{
if(visit[i]==0)
{
visit[i]=1;
second_cnt++;
}
if(second_cnt==n/2) break;
}
}
}
void first_solve()
{
vector <int> v;//存放具体数据大小
vector <int> pos;//存放每个数据的位置
for(int i=1;i<=n;i++)
{
if(visit[i]==1)
{
v.push_back(a[i]);
pos.push_back(i);//位置递增的
cout<<i<<" ";
}
}
cout<<endl;
// cout<<"V:"<<endl;
// for(int i=0; i<v.size(); i++) cout<<v[i]<<" ";
// cout<<endl<<"pos:"<<endl;
// for(int i=0; i<pos.size(); i++) cout<<pos[i]<<" ";
// cout<<endl;
sort(v.begin(),v.end());
for(int i=0;i<v.size();i++)
{
locate[v[i]]=pos[i];//更新位置
a[pos[i]]=v[i];
}
}
void second_solve()
{
vector <int> v;
vector <int> pos;
for(int i=n/4+1;i<=n;i++)
{
if(visit[i]==1)
{
v.push_back(a[i]);
pos.push_back(i);
cout<<i<<" ";
}
}
cout<<endl;
// cout<<"V:"<<endl;
// for(int i=0; i<v.size(); i++) cout<<v[i]<<" ";
// cout<<endl<<"pos:"<<endl;
// for(int i=0; i<pos.size(); i++) cout<<pos[i]<<" ";
// cout<<endl;
sort(v.begin(),v.end());
for(int i=0;i<v.size();i++)
{
locate[v[i]]=pos[i];
a[pos[i]]=v[i];
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
locate[a[i]]=i;
}
// check();
cout<<3<<endl;
first_insert();
first_solve();
// check();
for(int i=n/4+1;i<=n;i++) visit[i]=0;
second_insert();
second_solve();
//check();
for(int i=n/2+1;i<=n;i++) cout<<i<<" ";
cout<<endl;
return 0;
}
牛客18454B 题目链接:点击这里传送
题意:给出一个数n,问其能由几个回文数相加(不多于10),输出这些回文数
思路:每次找出一个尽可能大的小于等于n的回文数,将其减去 屁话
在写这个构造回文数时,我们通过可以一些特判
- 个位数,直接返回本身
- 十位数,返回11,22,33这种类型的东西
- 10 100 1000 ,直接返回n-1(这个其实无所谓,后面也能构造出来)
- 本身就是回文数,返回本身
每次构造时选取左边的(n+1)/2个数为基准进行构造。首先使其前(n+1)/2字符串构成的数字值减一,这样能保证构造出的串肯定小于原串。再对原串长度的奇偶性进行判断,如果是奇数,那么以上文提到的字符串作为基准构造回文串时要少构造一位。
他妈了个逼的傻逼牛客,一个莫名奇妙的玄学语句卡了老子两个小时,明明应该是完全一样的值一个能过一个AC不了(详见22,23行)
#include<bits/stdc++.h>
using namespace std;
//输入一个数字,问其可以由几个回文数相加
#define ll long long
ll t;
vector <ll> ans;
ll get_palindrome(ll n)//找出一个小于等于n的回文数
{
if(n<10) return n;//个位数
if(n>10&&n<100) return n/11*11;//十位数
string s=to_string(n);//正向
string temp=s;//逆向
reverse(temp.begin(),temp.end());
if(temp==s) return n;//本身就是回文数
if(stoll(temp)==1) return n-1;// 10 100 1000 1000 100000 这种,直接返回9999999999999就行
//以上均为特判
//另左边一半子串的数字大小-1,根据这个构造回文串可以保证构造出来的回文比原数小且尽可能大(11000这种情况很好用)
string left_half=s.substr(0,(s.size()+1)/2);
left_half=to_string(stoll(left_half)-1);
string answer=left_half;
// int half_len=s.length()/2-1;//右串的长度,如果是奇数的话少构造一位
int half_len=left_half.length()-s.length()%2-1;
for(int i=half_len;i>=0;i--) answer+=left_half[i];
return stoll(answer);
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>t;
while(t>0)
{
ll temp=get_palindrome(t);
ans.push_back(temp);
t-=temp;
}
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++) cout<<ans[i]<<endl;
return 0;
}