题目地址:https://codeforces.com/contest/1156
同时发布在本人的洛谷博客:https://www.luogu.org/blog/996-icu/
并没有去打这一场比赛,不过据说因为第一题数据的锅unrated了。
就只写前五题的题解好了,一个是五一这几天学校搞数论训练比较忙,数论太难了QAQ,主要还是第六题题解看了一会,标答代码也看了看但不是很懂。。。所以就这样愉快的决定放弃吧
A. Inscribed Figures
用草稿纸或者脑子找下规律就ok了,一个要注意的就是正方形+圆+三角形的时候会少一个点,判断模拟一下就行
B. Ugly Pairs
作为B题其实算比较麻烦的那种了,一不注意就会wa。
先观察样例,得出只有两种情况是no answer的
1、只有两种字符并且相邻
2、只有三种字符并且相邻
因此读入字符串的时候记录一下各个字符的数量,若是上述两种情况则可以直接输出no answer。
接下来考虑应该如何输出字符,据了解身边大佬不少用的是分奇偶位字母输出,本蒟蒻并没有想到。。。我的方法主要是观察abcd->cadb得出的,假设有n种字符(从小到大的顺序)时,先输出所有n/2+1位字符,再输出所有第一位字符,接着输出所有第n/2+2位字符,再输出所有第二位字符,一次类推,可保证在四个以上字符时答案正确,三个字符的时候则需要特判一下
#include
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 2010
typedef long long ll;
int T,n=26,let[30],ans,ind[30],noflag,letind;//let[]存各字符数量
char cha[maxn]={'0'};
int main()
{
cin>>T;
while (T--)
{
noflag=0,letind=0;
memset(let,0,sizeof(let));
cin>>(cha+1);
int len=strlen(cha)-1;
for (int i=1; i<=len; i++)
let[cha[i]-96]++;
for (int i=1; i<=n; i++)
if (let[i]!=0)
ind[++letind]=i;
if (letind==2)
for (int i=1; i<=n; i++)
if (let[i]>0 && let[i+1]>0)
noflag=1;
if (letind==3)
for (int i=1; i<=n; i++)
if (let[i]>0 && let[i+1]>0 && let[i+2]>0)
noflag=1;
if (noflag)
{
cout<<"No answer"<<endl;
continue;
}
if (letind==3)//特判只有三个字符的情况
{
if (ind[1]+1==ind[2])
{
while (let[ ind[1] ]--)
cout<<char(ind[1]+96);
while (let[ ind[3] ]--)
cout<<char(ind[3]+96);
while (let[ ind[2] ]--)
cout<<char(ind[2]+96);
}
else
{
while (let[ ind[2] ]--)
cout<<char(ind[2]+96);
while (let[ ind[1] ]--)
cout<<char(ind[1]+96);
while (let[ ind[3] ]--)
cout<<char(ind[3]+96);
}
cout<<endl;
continue;
}
if (letind%2==1)
{
while (let[ind[letind]]--)
cout<<char(ind[letind]+96);
}
for (int i=1; i<=letind/2; i++)
{
while (let[ind[i+letind/2]]--)
cout<<char(ind[i+letind/2]+96);
while (let[ind[i]]--)
cout<<char(ind[i]+96);
}
cout<<endl;
}return 0;
}
C. Match Points
开始很容易想成贪心取最接近当前元素+z的元素,但遇到例如:z取2,{1,5,10,11}的情况就会出问题。
开始我一直想着可以稍微修改一下仍以这样的贪心为主,改了好多次最好结果也仅仅是能撑到接近30个测试点而已…
看了一眼别人的做法才恍然大悟,考虑一个长度为n的字符串,则其答案上限应该为n/2,因此我们直接用两个指针分别指向第一位和第n/2+1位,移动左右指针遍历的时候判断一下就行了,可以直接看代码
#include
using namespace std;
#define inf 1e18
#define maxn 200100
typedef long long ll;
int n,z,num[maxn],ans;
int main()
{
cin>>n>>z;
for (int i=1; i<=n; i++)
cin>>num[i];
sort(num+1,num+1+n);
int l=1,r=n/2+1;
while (r<=n && l<=n/2)
{
if (num[r]-num[l]>=z)
{
ans++;l++;
}
r++;
}
cout<<ans<<endl;
return 0;
}
D. 0-1-Tree
据说能用并查集??反正我完全没想到,第一眼看到这题的时候就觉得应该是树形dp。
总体思路是用深度优先搜索找到叶子结点然后在回溯的过程进行动态规划,对于一个结点i,它的子树中合法的连续的边可以概括为四种情况
1、边中的点全为0(用dp[i][0]表示)
2、上半部分为0,下半部分为1(dp[i][1])
3、全为1(dp[i][2])
4、上半部分为1,下半部分为0(dp[i][3])
用buf[j]表示i号结点的子结点(以下记为to)状态边对dp[i][j]的贡献,考虑i的值为0时全0边的转移情况,则由i号结点向下连的全为0的边应当为dp[to][0]+1,因为对于第i号结点比to结点多了一个i->to的选择,其它几种情况结合代码稍微想想应该也能得出结果,因为懒就不赘述了。
在回溯的过程中对于i号结点,回溯结果是由其子边一条一条回溯过来的,假设现在i号结点已经回溯了部分边的结果,也就是dp[i][j]已经具有了部分边返回的值,对于新回溯的边,对答案的贡献要如何计算呢?
考虑返回的边全是0的情况,全是0的边可以与dp[i][]中已经记录的全0边,全1边,上0下1边结合产生贡献,且与全0边结合时正反方向结合都合法因此要乘以2,最后再加上自身不结合的贡献(因为是全0边所以自身正反方向也都合法,因此自身贡献也要乘以2),具体公式如下
ans+=buf[0]*(2*dp[i][0]+dp[i][1]+dp[i][2])+2*buf[0]
其它几种贡献计算公式也差不多,具体可以参考代码
#include
using namespace std;
#define inf 1e18
#define maxn 200100
typedef long long ll;
ll n,ans,dp[maxn][4],buf[4];
int x1,x2,x3;
struct bian
{
int to,v;
};
vector<bian> edge[maxn];
int dfs(int now, int fa)
{
for (int i=0; i<edge[now].size(); i++)
{
if (edge[now][i].to==fa) continue;
int to=edge[now][i].to;
dfs(edge[now][i].to,now);
memset(buf,0,sizeof(buf));
if (edge[now][i].v==0)
{
buf[0]=dp[to][0]+1;
buf[1]=dp[to][1]+dp[to][2];
}
else
{
buf[2]=dp[to][2]+1;
buf[3]=dp[to][0]+dp[to][3];
}
ans+=buf[0]*(2*dp[now][0]+dp[now][1]+dp[now][2])+2*buf[0];
ans+=buf[1]*dp[now][0]+buf[1];
ans+=buf[2]*(2*dp[now][2]+dp[now][0]+dp[now][3])+2*buf[2];
ans+=buf[3]*dp[now][2]+buf[3];
for (int j=0; j<4; j++)
dp[now][j]+=buf[j];
}
return 0;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for (int i=1; i<n; i++)
{
cin>>x1>>x2>>x3;
edge[x1].push_back((bian){x2,x3});
edge[x2].push_back((bian){x1,x3});
}
dfs(1,0);
cout<<ans<<endl;
return 0;
}
E. Special Segments of Permutation
其实我觉得这题比上题好写多了,不知道为啥过的人还少一些,关键点想到后几分钟就可以a掉(d题知道要树形dp的情况下我还是调了挺久还wa了几发…)
2e5的数据量一看就觉得应该是O(nlogn)的解法,但是盯了快十分钟也没想到什么算法可以做到这个时间复杂度。突然注意到题目里要求的是两端和等于区间内最大值的数量,这意味着对于某个数字要找其左右侧两端值和与它相等时左右侧界限是左侧和右侧遇到的第一个比其大的值。
我们从大往小枚举作为区间内最大值的数,每次在其左右界限内寻找是否有合法的点对,设该点i距离其左界限为a,距离右界限为b,则所能找到的合法点对的上限为min(a,b)/2,因此可以直接在距离较小的那一侧枚举数字,检测i-j是否位于另一侧区域的界限内。
一看这样算法时间复杂度不还是O(n²)吗?实际上可以这么想,枚举第一个“区间最大值”时,最多需要计算次数为n/2,再枚举第二个和第三个时最多计算次数为n/4(或者更严谨的说两者加起来最多计算n/2次),这部分算法的最终时间复杂度就是O(nlogn)。考虑一个区间数轴,每一次枚举作为区间最大值的数都会将某个区间分成两部分,是不是很像2分的过程呢?
因为我还用了个lower_bound()寻找i-j(差值)的位置,最终复杂度应该是O(n*logn*longn),属于完全可接受的范围
#include
using namespace std;
#define inf 1e18
#define maxn 200100
typedef long long ll;
int n,ans,num[maxn],xuhao[maxn];
map<int,int> mp;
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
{
cin>>num[i];
xuhao[num[i]]=i;
}
mp[0]=1,mp[n+1]=1;
for (int i=n; i>=3; i--)
{
map<int,int>::iterator it=mp.upper_bound(xuhao[i]);
int r=it->first;
it--;
int l=it->first;
if (r-xuhao[i]<=xuhao[i]-l)
for (int j=xuhao[i]+1; j<r; j++)
if (xuhao[i-num[j]]>l && xuhao[i-num[j]]<xuhao[i])
ans++;
if (r-xuhao[i]>xuhao[i]-l)
for (int j=xuhao[i]-1; j>l; j--)
if (xuhao[i-num[j]]<r && xuhao[i-num[j]]>xuhao[i])
ans++;
mp[xuhao[i]]=1;
}
cout<<ans<<endl;
return 0;
}